-- UI.lua
-- Dynamic UI Elements

local addon, ns = ...
local Hekili = _G[ addon ]

local class = Hekili.Class
local state = Hekili.State

local FindUnitBuffByID, FindUnitDebuffByID = ns.FindUnitBuffByID, ns.FindUnitDebuffByID

-- Atlas/Textures
local AddTexString, GetTexString, AtlasToString, GetAtlasFile, GetAtlasCoords = ns.AddTexString, ns.GetTexString, ns.AtlasToString, ns.GetAtlasFile, ns.GetAtlasCoords

local frameStratas = ns.FrameStratas
local getInverseDirection = ns.getInverseDirection
local multiUnpack = ns.multiUnpack
local orderedPairs = ns.orderedPairs
local round = ns.round

local IsCurrentItem = C_Item.IsCurrentItem
local IsUsableItem = C_Item.IsUsableItem
local IsCurrentSpell = C_Spell.IsCurrentSpell
local GetItemCooldown = C_Item.GetItemCooldown
local GetItemInfoInstant = C_Item.GetItemInfoInstant
local GetSpellTexture = C_Spell.GetSpellTexture
local IsUsableSpell = C_Spell.IsSpellUsable
local IsSpellOverlayed = C_SpellActivationOverlay.IsSpellOverlayed

local GetSpellCooldown = function( spellID )
    local spellCooldownInfo = C_Spell.GetSpellCooldown( spellID )
    if spellCooldownInfo then
        return spellCooldownInfo.startTime, spellCooldownInfo.duration, spellCooldownInfo.isEnabled, spellCooldownInfo.modRate
    end
    return 0, 0, false, 0
end

local GetSpecialization = C_SpecializationInfo.GetSpecialization
local GetSpecializationInfo = C_SpecializationInfo.GetSpecializationInfo

local floor, format, insert = math.floor, string.format, table.insert

local HasVehicleActionBar, HasOverrideActionBar, IsInPetBattle, UnitHasVehicleUI, UnitOnTaxi = HasVehicleActionBar, HasOverrideActionBar, C_PetBattles.IsInBattle, UnitHasVehicleUI, UnitOnTaxi
local Tooltip = ns.Tooltip

local Masque, MasqueGroup
local _

-- FPS smoothing system for stable budget calculations
local fpsTracker = {
    samples         = {},  -- Sliding window of FPS samples
    maxSamples      = 30,  -- 30 samples for smoothing
    smoothedFPS     = 60,  -- Current smoothed FPS value
    lastUpdate      = 0,   -- Last update time
    updateInterval  = 0.1, -- Update every 100ms
    index           = 1,   -- Ring tracker
    count           = 0,   -- Total samples
    sum             = 0    -- Sum of all samples
}

local function updateSmoothedFPS()
    local now = GetTime()
    if now - fpsTracker.lastUpdate >= fpsTracker.updateInterval then
        local currentFPS = GetFramerate()

        -- If overwriting an old sample, subtract it first
        if fpsTracker.count == fpsTracker.maxSamples then
            fpsTracker.sum = fpsTracker.sum - fpsTracker.samples[ fpsTracker.index ]
        else
            fpsTracker.count = fpsTracker.count + 1
        end

        -- Add to sliding window
        fpsTracker.samples[ fpsTracker.index ] = currentFPS
        fpsTracker.sum = fpsTracker.sum + currentFPS

        -- Shift the index
        fpsTracker.index = ( fpsTracker.index % fpsTracker.maxSamples ) + 1

        -- Calculate smoothed average
        fpsTracker.smoothedFPS = fpsTracker.sum / fpsTracker.count
        fpsTracker.lastUpdate = now
    end

    return fpsTracker.smoothedFPS
end

-- Expose smoothed FPS for use in other places
function Hekili.GetSmoothedFPS()
    return updateSmoothedFPS()
end

-- Calculate frame budget based on user percentage
local function calculateFrameBudget()
    local smoothedFPS = updateSmoothedFPS()
    local frameBudget = Hekili.DB.profile.performance.frameBudget or 0.7
    -- local rawFPS = GetFramerate()

    -- Calculate frame time
    local frameTime = 1000 / math.max( smoothedFPS, 30 ) -- min 30 FPS

    -- Apply user percentage directly to frame time
    local userBudget = frameTime * frameBudget

    -- Debug output
    -- print(string.format("[Hekili Budget] Setting: %d%%, Raw FPS: %.1f, Smoothed FPS: %.1f, Frame Time: %.2fms, Budget: %.2fms",
    --     frameBudget, rawFPS, smoothedFPS, frameTime, userBudget))

    return userBudget
end

function Hekili:GetScale()
    return PixelUtil.GetNearestPixelSize( 1, PixelUtil.GetPixelToUIUnitFactor(), 1 )
end


local movementData = {}

local function startScreenMovement(frame)
    movementData.origX,movementData.origY = select( 4, frame:GetPoint() )
    frame:StartMoving()
    movementData.fromX, movementData.fromY = select( 4, frame:GetPoint() )
    frame.Moving = true
end

local function stopScreenMovement(frame)
    local resolution = C_VideoOptions.GetCurrentGameWindowSize()
    local scrW, scrH = resolution.x, resolution.y

    local scale, pScale = Hekili:GetScale(), UIParent:GetScale()

    scrW = scrW / ( scale * pScale )
    scrH = scrH / ( scale * pScale )

    local limitX = ( scrW - frame:GetWidth() ) / 2
    local limitY = ( scrH - frame:GetHeight() ) / 2

    movementData.toX, movementData.toY = select( 4, frame:GetPoint() )
    frame:StopMovingOrSizing()
    frame.Moving = false
    frame:ClearAllPoints()
    frame:SetPoint( "CENTER", nil, "CENTER",
        max(-limitX, min(limitX, movementData.origX + (movementData.toX - movementData.fromX))),
        max(-limitY, min(limitY, movementData.origY + (movementData.toY - movementData.fromY))) )
    Hekili:SaveCoordinates()
end

local function Mover_OnMouseUp(self, btn)
    local obj = self.moveObj or self

    if (btn == "LeftButton" and obj.Moving) then
        stopScreenMovement(obj)
        Hekili:SaveCoordinates()
    elseif btn == "RightButton" then
        if obj:GetName() == "HekiliNotification" then
            LibStub( "AceConfigDialog-3.0" ):SelectGroup( "Hekili", "displays", "nPanel" )
            return
        elseif obj and obj.id then
            LibStub( "AceConfigDialog-3.0" ):SelectGroup( "Hekili", "displays", obj.id )
            return
        end
    end
end

local function Mover_OnMouseDown( self, btn )
    local obj = self.moveObj or self

    if Hekili.Config and btn == "LeftButton" and not obj.Moving then
        startScreenMovement(obj)
    end
end

local function Button_OnMouseUp( self, btn )
    local display = self.display
    local mover = _G[ "HekiliDisplay" .. display ]

    if (btn == "LeftButton" and mover.Moving) then
        stopScreenMovement(mover)

    elseif (btn == "RightButton") then
        if mover.Moving then
            stopScreenMovement(mover)
        end
        local mouseInteract = Hekili.Pause or Hekili.Config
        for i = 1, #ns.UI.Buttons do
            for j = 1, #ns.UI.Buttons[i] do
                ns.UI.Buttons[i][j]:EnableMouse(mouseInteract)
            end
        end
        ns.UI.Notification:EnableMouse( Hekili.Config )
        -- Hekili:SetOption( { "locked" }, true )
        GameTooltip:Hide()

    end

    Hekili:SaveCoordinates()
end

local function Button_OnMouseDown(self, btn)
    local display = self.display
    local mover = _G[ "HekiliDisplay" .. display ]

    if Hekili.Config and btn == "LeftButton" and not mover.Moving then
        startScreenMovement(mover)
    end
end


function ns.StartConfiguration( external )
    Hekili.Config = true

    local scaleFactor = Hekili:GetScale()
    local ccolor = RAID_CLASS_COLORS[ select( 2, UnitClass( "player" ) ) ]

    -- Notification Panel
    ns.UI.Notification.Mover = ns.UI.Notification.Mover or CreateFrame( "Frame", "HekiliNotificationMover", ns.UI.Notification, "BackdropTemplate" )
    ns.UI.Notification.Mover:SetAllPoints( HekiliNotification )
    ns.UI.Notification.Mover:SetBackdrop( {
        bgFile = "Interface/Buttons/WHITE8X8",
        edgeFile = "Interface/Buttons/WHITE8X8",
        tile = false,
        tileSize = 0,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 }
    } )

    ns.UI.Notification.Mover:SetBackdropColor( 0, 0, 0, .8 )
    ns.UI.Notification.Mover:SetBackdropBorderColor( ccolor.r, ccolor.g, ccolor.b, 1 )
    ns.UI.Notification.Mover:Show()

    local f = ns.UI.Notification.Mover

    if not f.Header then
        f.Header = f:CreateFontString( "HekiliNotificationHeader", "OVERLAY", "GameFontNormal" )
        local path = f.Header:GetFont()
        f.Header:SetFont( path, 18, "OUTLINE" )
    end
    f.Header:SetAllPoints( HekiliNotificationMover )
    f.Header:SetText( "Notifications" )
    f.Header:SetJustifyH( "CENTER" )
    f.Header:Show()

    if HekiliNotificationMover:GetFrameLevel() > HekiliNotification:GetFrameLevel() then
        local orig = HekiliNotificationMover:GetFrameLevel()
        HekiliNotification:SetFrameLevel(orig)
        HekiliNotificationMover:SetFrameLevel(orig-1)
    end

    ns.UI.Notification:EnableMouse( true )
    ns.UI.Notification:SetMovable( true )

    HekiliNotification:SetScript( "OnMouseDown", Mover_OnMouseDown )
    HekiliNotification:SetScript( "OnMouseUp", Mover_OnMouseUp )
    HekiliNotification:SetScript( "OnEnter", function( self )
        local H = Hekili

        if H.Config then
            Tooltip:SetOwner( self, "ANCHOR_TOPRIGHT" )

            Tooltip:SetText( "Hekili: Notifications" )
            Tooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 )
            Tooltip:AddLine( "Right-click to open Notification panel settings.", 1, 1, 1 )
            Tooltip:Show()
        end
    end )
    HekiliNotification:SetScript( "OnLeave", function(self)
        Tooltip:Hide()
    end )

    Hekili:ProfileFrame( "NotificationFrame", HekiliNotification )

    for i, v in pairs( ns.UI.Displays ) do
        if v.Backdrop then
            v.Backdrop:Hide()
        end

        if v.Header then
            v.Header:Hide()
        end

        if ns.UI.Buttons[ i ][ 1 ] and Hekili.DB.profile.displays[ i ] then
            -- if not Hekili:IsDisplayActive( i ) then v:Show() end

            v.Backdrop = v.Backdrop or CreateFrame( "Frame", v:GetName().. "_Backdrop", UIParent, "BackdropTemplate" )
            v.Backdrop:ClearAllPoints()

            if not v:IsAnchoringRestricted() then
                v:EnableMouse( true )
                v:SetMovable( true )

                for id, btn in ipairs( ns.UI.Buttons[ i ] ) do
                    btn:EnableMouse( false )
                end

                local left, right, top, bottom = v:GetPerimeterButtons()
                if left and right and top and bottom then
                    v.Backdrop:SetPoint( "LEFT", left, "LEFT", -2, 0 )
                    v.Backdrop:SetPoint( "RIGHT", right, "RIGHT", 2, 0 )
                    v.Backdrop:SetPoint( "TOP", top, "TOP", 0, 2 )
                    v.Backdrop:SetPoint( "BOTTOM", bottom, "BOTTOM", 0, -2 )
                else
                    v.Backdrop:SetWidth( v:GetWidth() + 2 )
                    v.Backdrop:SetHeight( v:GetHeight() + 2 )
                    v.Backdrop:SetPoint( "CENTER", v, "CENTER" )
                end
            end

            v.Backdrop:SetFrameStrata( v:GetFrameStrata() )
            v.Backdrop:SetFrameLevel( v:GetFrameLevel() + 1 )

            v.Backdrop.moveObj = v

            v.Backdrop:SetBackdrop( {
                bgFile = "Interface/Buttons/WHITE8X8",
                edgeFile = "Interface/Buttons/WHITE8X8",
                tile = false,
                tileSize = 0,
                edgeSize = 1,
                insets = { left = 0, right = 0, top = 0, bottom = 0 }
            } )

            local ccolor = RAID_CLASS_COLORS[ select(2, UnitClass("player")) ]

            if Hekili:IsDisplayActive( v.id, true ) then
                v.Backdrop:SetBackdropBorderColor( ccolor.r, ccolor.g, ccolor.b, 1 )
            else
                v.Backdrop:SetBackdropBorderColor( 0.5, 0.5, 0.5, 0.5 )
            end
            v.Backdrop:SetBackdropColor( 0, 0, 0, 0.8 )
            v.Backdrop:Show()

            v.Backdrop:SetScript( "OnMouseDown", Mover_OnMouseDown )
            v.Backdrop:SetScript( "OnMouseUp", Mover_OnMouseUp )
            v.Backdrop:SetScript( "OnEnter", function( self )
                local H = Hekili

                if H.Config then
                    Tooltip:SetOwner( self, "ANCHOR_TOPRIGHT" )

                    Tooltip:SetText( "Hekili: " .. i )
                    Tooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 )
                    Tooltip:AddLine( "Right-click to open " .. i .. " display settings.", 1, 1, 1 )
                    if not H:IsDisplayActive( i, true ) then Tooltip:AddLine( "This display is not currently active.", 0.5, 0.5, 0.5 ) end
                    Tooltip:Show()
                end
            end )
            v.Backdrop:SetScript( "OnLeave", function( self )
                Tooltip:Hide()
            end )
            v:Show()

            if not v.Header then
                v.Header = v.Backdrop:CreateFontString( "HekiliDisplay" .. i .. "Header", "OVERLAY", "GameFontNormal" )
                local path = v.Header:GetFont()
                v.Header:SetFont( path, 18, "OUTLINE" )
            end
            v.Header:ClearAllPoints()
            v.Header:SetAllPoints( v.Backdrop )

            if i == "Defensives" then v.Header:SetText( AtlasToString( "nameplates-InterruptShield" ) )
            elseif i == "Interrupts" then v.Header:SetText( AtlasToString( "voicechat-icon-speaker-mute" ) )
            elseif i == "Cooldowns" then v.Header:SetText( AtlasToString( "chromietime-32x32" ) )
            else v.Header:SetText( i ) end

            v.Header:SetJustifyH("CENTER")
            v.Header:Show()
        else
            v:Hide()
        end
    end

    if not external then
        if not Hekili.OptionsReady then Hekili:RefreshOptions() end

        local ACD = LibStub( "AceConfigDialog-3.0" )
        ACD:SetDefaultSize( "Hekili", 800, 608 )
        ACD:Open( "Hekili" )

        local oFrame = ACD.OpenFrames["Hekili"].frame
        oFrame:SetResizeBounds( 800, 120 )

        ns.OnHideFrame = ns.OnHideFrame or CreateFrame( "Frame" )
        ns.OnHideFrame:SetParent( oFrame )
        ns.OnHideFrame:SetScript( "OnHide", function(self)
            ns.StopConfiguration()
            self:SetScript( "OnHide", nil )
            self:SetParent( nil )
            if not InCombatLockdown() then
                collectgarbage()
                Hekili:UpdateDisplayVisibility()
            else
                C_Timer.After( 0, function() Hekili:UpdateDisplayVisibility() end )
            end
        end )

        if not ns.OnHideFrame.firstTime then
            ACD:SelectGroup( "Hekili", "packs" )
            ACD:SelectGroup( "Hekili", "displays" )
            ACD:SelectGroup( "Hekili", "displays", "Multi" )
            ACD:SelectGroup( "Hekili", "general" )
            ns.OnHideFrame.firstTime = true
        end

        Hekili:ProfileFrame( "CloseOptionsFrame", ns.OnHideFrame )
    end

    Hekili:UpdateDisplayVisibility()
end

function Hekili:OpenConfiguration()
    ns.StartConfiguration()
end

function ns.StopConfiguration()
    Hekili.Config = false

    local scaleFactor = Hekili:GetScale()
    local mouseInteract = Hekili.Pause

    for id, display in pairs( Hekili.DisplayPool ) do
        display:EnableMouse( false )
        if not display:IsAnchoringRestricted() then display:SetMovable( true ) end

        -- v:SetBackdrop( nil )
        if display.Header then
            display.Header:Hide()
        end
        if display.Backdrop then
            display.Backdrop:Hide()
        end

        for i, btn in ipairs( display.Buttons ) do
            btn:EnableMouse( mouseInteract )
            btn:SetMovable( false )
        end
    end

    HekiliNotification:EnableMouse( false )
    HekiliNotification:SetMovable( false )
    HekiliNotification.Mover:Hide()
    -- HekiliNotification.Mover.Header:Hide()
end

local function MasqueUpdate( Addon, Group, SkinID, Gloss, Backdrop, Colors, Disabled )
    if Disabled then
        for dispID, display in ipairs( ns.UI.Buttons ) do
            for btnID, button in ipairs( display ) do
                button.__MSQ_NormalTexture:Hide()
                button.Texture:SetAllPoints( button )
            end
        end
    end
end


do
    ns.UI.Menu = ns.UI.Menu or CreateFrame( "Frame", "HekiliMenu", UIParent, "UIDropDownMenuTemplate" )
    local menu = ns.UI.Menu

    Hekili:ProfileFrame( "HekiliMenu", menu )

    menu.info = {}

    menu.AddButton = UIDropDownMenu_AddButton
    menu.AddSeparator = UIDropDownMenu_AddSeparator

    local function SetDisplayMode( mode )
        Hekili.DB.profile.toggles.mode.value = mode
        if WeakAuras and WeakAuras.ScanEvents then WeakAuras.ScanEvents( "HEKILI_TOGGLE", "mode", mode ) end
        if ns.UI.Minimap then ns.UI.Minimap:RefreshDataText() end

        Hekili:UpdateDisplayVisibility()
        Hekili:ForceUpdate( "HEKILI_TOGGLE", true )
    end

    local function IsDisplayMode( p, mode )
        return Hekili.DB.profile.toggles.mode.value == mode
    end

    local menuData = {
        {
            isTitle = 1,
            text = "Hekili",
            notCheckable = 1,
        },

        {
            text = "正常开启", -- modify for pengchao
            func = function () Hekili:Toggle() end,
            checked = function () return Hekili.DB.profile.enabled end,
        },

        {
            text = "暂停(默认快捷键R)", -- modify for pengchao
            func = function () return Hekili:TogglePause() end,
            checked = function () return Hekili.Pause end,
        },

        {
            isSeparator = 1,
        },

        {
            isTitle = 1,
            text = "输出模式", -- modify for pengchao
            notCheckable = 1,
        },

        {
            text = "自动判断", -- modify for pengchao
            func = function () SetDisplayMode( "automatic" ) end,
            checked = function () return IsDisplayMode( p, "automatic" ) end,
        },

        {
            text = "强行单体", -- modify for pengchao
            func = function () SetDisplayMode( "single" ) end,
            checked = function () return IsDisplayMode( p, "single" ) end,
        },

        {
            text = "强行群体(AOE)", -- modify for pengchao
            func = function () SetDisplayMode( "aoe" ) end,
            checked = function () return IsDisplayMode( p, "aoe" ) end,
        },
		
		-- remove for pengchao
        -- {
        --     text = "Dual",
        --     func = function () SetDisplayMode( "dual" ) end,
        --     checked = function () return IsDisplayMode( p, "dual" ) end,
        -- },

        -- {
        --     text = "Reactive",
        --     func = function () SetDisplayMode( "reactive" ) end,
        --     checked = function () return IsDisplayMode( p, "reactive" ) end,
        -- },
		-- remove for pengchao
		
        {
            isSeparator = 1,
        },

        {
            isTitle = 1,
            text = "辅助开关", -- modify for pengchao
            notCheckable = 1,
        },

        {
            text = "长CD爆发自动释放(60秒以上技能)", -- modify for pengchao
            func = function() Hekili:FireToggle( "cooldowns" ); ns.UI.Minimap:RefreshDataText() end,
            checked = function () return Hekili.DB.profile.toggles.cooldowns.value end,
        },

        {
            text = "短CD爆发自动释放(60秒以下技能)", -- modify for pengchao
            func = function() Hekili:FireToggle( "essences" ); ns.UI.Minimap:RefreshDataText() end,
            checked = function () return Hekili.DB.profile.toggles.essences.value end,
        },

        {
            text = "自动打断(打断阈值默认还剩余0.75)", -- modify for pengchao
            func = function() Hekili:FireToggle( "interrupts" ); ns.UI.Minimap:RefreshDataText() end,
            checked = function () return Hekili.DB.profile.toggles.interrupts.value end,
        },

        {
            text = "保命技能", -- modify for pengchao
            func = function() Hekili:FireToggle( "defensives" ); ns.UI.Minimap:RefreshDataText() end,
            checked = function () return Hekili.DB.profile.toggles.defensives.value end,
        },
		
		-- add for pengchao start
		{
		    text = "指向性施法作用在鼠标位置(修改需/RL或/reload生效)",
		    func = function() Hekili:FireToggle( "auto_spell_to_mouse_here" ); ns.UI.Minimap:RefreshDataText() end,
		    checked = function () return Hekili.DB.profile.toggles.auto_spell_to_mouse_here.value end,
		},
		-- add for pengchao end

        -- {
        --     text = "Potions",
        --     func = function() Hekili:FireToggle( "potions" ); ns.UI.Minimap:RefreshDataText() end,
        --     checked = function () return Hekili.DB.profile.toggles.potions.value end,
        -- }
    }

    local specsParsed = false
    menu.args = {}

    UIDropDownMenu_SetDisplayMode( menu, "MENU" )

    function menu:initialize( level, list )
        if not level and not list then
            return
        end

        if level == 1 then
            if not specsParsed then
                -- Add specialization toggles where applicable.
                for i, spec in pairs( Hekili.Class.specs ) do
                    if i > 0 then
                        insert( menuData, {
                            isSeparator = 1,
                            hidden = function () return Hekili.State.spec.id ~= i end,
                        } )
                        insert( menuData, {
                            isTitle = 1,
                            text = spec.name,
                            notCheckable = 1,
                            hidden = function () return Hekili.State.spec.id ~= i end,
                        } )
                        insert( menuData, {
                            text = "|TInterface\\Addons\\Hekili\\Textures\\Cycle:0|t Recommend Target Swaps",
                            tooltipTitle = "|TInterface\\Addons\\Hekili\\Textures\\Cycle:0|t Recommend Target Swaps",
                            tooltipText = "If checked, the |TInterface\\Addons\\Hekili\\Textures\\Cycle:0|t indicator may be displayed which means you should use the ability on a different target.",
                            tooltipOnButton = true,
                            func = function ()
                                local spec = rawget( Hekili.DB.profile.specs, i )
                                if spec then
                                    spec.cycle = not spec.cycle
                                    if Hekili.DB.profile.notifications.enabled then
                                        Hekili:Notify( "Recommend Target Swaps: " .. ( spec.cycle and "ON" or "OFF" ) )
                                    else
                                        Hekili:Print( "Recommend Target Swaps: " .. ( spec.cycle and " |cFF00FF00ENABLED|r." or " |cFFFF0000DISABLED|r." ) )
                                    end
                                end
                            end,
                            checked = function ()
                                local spec = rawget( Hekili.DB.profile.specs, i )
                                return spec.cycle
                            end,
                            hidden = function () return Hekili.State.spec.id ~= i end,
                        } )

                        local potionMenu = {
                            text = "|T967533:0|t Preferred Potion",
                            tooltipTitle = "|T967533:0|t Preferred Potion",
                            tooltipText = "Select the potion you would like to use when the |cFFFFD100Potions|r toggle is enabled.",
                            tooltipOnButton = true,
                            hasArrow = true,
                            menuList = {},
                            notCheckable = true,
                            hidden = function () return Hekili.State.spec.id ~= i end,
                        }

                        for k, v in orderedPairs( class.potionList ) do
                            insert( potionMenu.menuList, {
                                text = v,
                                func = function ()
                                    Hekili.DB.profile.specs[ Hekili.State.spec.id ].potion = k
                                    for _, display in pairs( Hekili.DisplayPool ) do
                                        display:OnEvent( "HEKILI_MENU" )
                                    end
                                end,
                                checked = function ()
                                    return Hekili.DB.profile.specs[ Hekili.State.spec.id ].potion == k
                                end,
                            } )
                        end

                        insert( menuData, potionMenu )

                        -- Check for Toggles.
                        for n, setting in pairs( spec.settings ) do
                            if setting.info and ( not setting.info.arg or setting.info.arg() ) then
                                if setting.info.type == "toggle" then
                                    local name = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
                                    local submenu
                                    submenu = {
                                        text = name,
                                        tooltipTitle = name,
                                        tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
                                        tooltipOnButton = true,
                                        func = function ()
                                            menu.args[1] = setting.name
                                            setting.info.set( menu.args, not setting.info.get( menu.args ) )

                                            local nm = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name

                                            if Hekili.DB.profile.notifications.enabled then
                                                Hekili:Notify( nm .. ": " .. ( setting.info.get( menu.args ) and "ON" or "OFF" ) )
                                            else
                                                Hekili:Print( nm .. ": " .. ( setting.info.get( menu.args ) and " |cFF00FF00ENABLED|r." or " |cFFFF0000DISABLED|r." ) )
                                            end

                                            submenu.text = nm
                                            submenu.tooltipTitle = nm
                                            submenu.tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc
                                        end,
                                        checked = function ()
                                            menu.args[1] = setting.name
                                            return setting.info.get( menu.args )
                                        end,
                                        hidden = function () return Hekili.State.spec.id ~= i end,
                                    }
                                    insert( menuData, submenu )

                                elseif setting.info.type == "select" then
                                    local name = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
                                    local submenu
                                    submenu = {
                                        text = name,
                                        tooltipTitle = name,
                                        tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
                                        tooltipOnButton = true,
                                        hasArrow = true,
                                        menuList = {},
                                        notCheckable = true,
                                        hidden = function () return Hekili.State.spec.id ~= i end,
                                    }

                                    local values = setting.info.values
                                    if type( values ) == "function" then values = values() end

                                    if values then
                                        if setting.info.sorting then
                                            for _, k in orderedPairs( setting.info.sorting ) do
                                                local v = values[ k ]
                                                insert( submenu.menuList, {
                                                    text = v,
                                                    func = function ()
                                                        menu.args[1] = setting.name
                                                        setting.info.set( menu.args, k )

                                                        local nm = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
                                                        submenu.text = nm
                                                        submenu.tooltipTitle = nm
                                                        submenu.tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc

                                                        for k, v in pairs( Hekili.DisplayPool ) do
                                                            v:OnEvent( "HEKILI_MENU" )
                                                        end
                                                    end,
                                                    checked = function ()
                                                        menu.args[1] = setting.name
                                                        return setting.info.get( menu.args ) == k
                                                    end,
                                                    hidden = function () return Hekili.State.spec.id ~= i end,
                                                } )
                                            end
                                        else
                                            for k, v in orderedPairs( values ) do
                                                insert( submenu.menuList, {
                                                    text = v,
                                                    func = function ()
                                                        menu.args[1] = setting.name
                                                        setting.info.set( menu.args, k )

                                                        local nm = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name
                                                        submenu.text = nm
                                                        submenu.tooltipTitle = nm
                                                        submenu.tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc

                                                        for k, v in pairs( Hekili.DisplayPool ) do
                                                            v:OnEvent( "HEKILI_MENU" )
                                                        end
                                                    end,
                                                    checked = function ()
                                                        menu.args[1] = setting.name
                                                        return setting.info.get( menu.args ) == k
                                                    end,
                                                    hidden = function () return Hekili.State.spec.id ~= i end,
                                                } )
                                            end
                                        end
                                    end

                                    insert( menuData, submenu )

                                elseif setting.info.type == "range" then

                                    local submenu = {
                                        text = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
                                        tooltipTitle = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
                                        tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
                                        tooltipOnButton = true,
                                        notCheckable = true,
                                        hidden = function () return Hekili.State.spec.id ~= i end,
                                        hasArrow = true,
                                        menuList = {}
                                    }

                                    local slider = {
                                        text = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
                                        tooltipTitle = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name,
                                        tooltipText = type( setting.info.desc ) == "function" and setting.info.desc() or setting.info.desc,
                                        tooltipOnButton = true,
                                        notCheckable = true,
                                        hidden = function () return Hekili.State.spec.id ~= i end,
                                    }
                                    local cn = "HekiliSpec" .. i .. "Option" .. n
                                    local cf = CreateFrame( "Frame", cn, UIParent, "HekiliPopupDropdownRangeTemplate" )

                                    cf.Slider:SetAccessorFunction( function()
                                        menu.args[1] = setting.name
                                        return setting.info.get( menu.args )
                                    end )

                                    cf.Slider:SetMutatorFunction( function( val )
                                        menu.args[1] = setting.name
                                        return setting.info.set( menu.args, val )
                                    end )

                                    cf.Slider:SetMinMaxValues( setting.info.min, setting.info.max )
                                    cf.Slider:SetValueStep( setting.info.step or 1 )
                                    cf.Slider:SetObeyStepOnDrag( true )

                                    cf.Slider:SetScript( "OnEnter", function( self )
                                        local tooltip = GetAppropriateTooltip()
                                        tooltip:SetOwner( cf.Slider, "ANCHOR_RIGHT", 0, 2 )
                                        GameTooltip_SetTitle( tooltip, slider.tooltipTitle )
                                        GameTooltip_AddNormalLine( tooltip, slider.tooltipText, true )
                                        tooltip:Show()
                                    end )

                                    cf.Slider:SetScript( "OnLeave", function( self )
                                        GameTooltip:Hide()
                                    end )

                                    slider.customFrame = cf

                                    insert( submenu.menuList, slider )

                                    --[[ local low, high, step = setting.info.min, setting.info.max, setting.info.step
                                    local fractional, factor = step < 1, 1 / step

                                    if fractional then
                                        low = low * factor
                                        high = high * factor
                                        step = step * factor
                                    end

                                    if ceil( ( high - low ) / step ) > 20 then
                                        step = ceil( ( high - low ) / 20 )
                                        if step % ( setting.info.step or 1 ) ~= 0 then
                                            step = step - ( step % ( setting.info.step or 1 ) )
                                        end
                                    end

                                    for j = low, high, step do
                                        local actual = j / factor
                                        insert( submenu.menuList, {
                                            text = tostring( actual ),
                                            func = function ()
                                                menu.args[1] = setting.name
                                                setting.info.set( menu.args, actual )

                                                local name = type( setting.info.name ) == "function" and setting.info.name() or setting.info.name

                                                if Hekili.DB.profile.notifications.enabled then
                                                    Hekili:Notify( name .. " set to |cFF00FF00" .. actual .. "|r." )
                                                else
                                                    Hekili:Print( name .. " set to |cFF00FF00" .. actual .. "|r." )
                                                end
                                            end,
                                            checked = function ()
                                                menu.args[1] = setting.name
                                                return setting.info.get( menu.args ) == actual
                                            end,
                                            hidden = function () return Hekili.State.spec.id ~= i end,
                                        } )
                                    end ]]

                                    insert( menuData, submenu )
                                end
                            end
                        end
                    end
                end
                specsParsed = true
            end
        end

        local use = list or menuData
        local classic = Hekili.IsClassic()

        for i, data in ipairs( use ) do
            data.classicChecks = classic

            if not data.hidden or ( type( data.hidden ) == 'function' and not data.hidden() ) then
                if data.isSeparator then
                    menu.AddSeparator( level )
                else
                    menu.AddButton( data, level )
                end
            end
        end
    end
end





do
    ns.UI.Displays = ns.UI.Displays or {}
    local dPool = ns.UI.Displays
    Hekili.DisplayPool = dPool

    local alphaUpdateEvents = {
        PET_BATTLE_OPENING_START = 1,
        PET_BATTLE_CLOSE = 1,
        BARBER_SHOP_OPEN = 1,
        BARBER_SHOP_CLOSE = 1,

        PLAYER_GAINS_VEHICLE_DATA = 1,
        PLAYER_LOSES_VEHICLE_DATA = 1,
        UNIT_ENTERING_VEHICLE = 1,
        UNIT_ENTERED_VEHICLE = 1,
        UNIT_EXITED_VEHICLE = 1,
        UNIT_EXITING_VEHICLE = 1,
        VEHICLE_ANGLE_SHOW = 1,
        VEHICLE_UPDATE = 1,
        UPDATE_VEHICLE_ACTIONBAR = 1,
        UPDATE_OVERRIDE_ACTIONBAR = 1,
        CLIENT_SCENE_OPENED = 1,
        CLIENT_SCENE_CLOSED = 1,
        -- UNIT_FLAGS = 1,

        PLAYER_TARGET_CHANGED = 1,

        PLAYER_ENTERING_WORLD = 1,
        PLAYER_REGEN_ENABLED = 1,
        PLAYER_REGEN_DISABLED = 1,

        ACTIVE_TALENT_GROUP_CHANGED = 1,

        ZONE_CHANGED = 1,
        ZONE_CHANGED_INDOORS = 1,
        ZONE_CHANGED_NEW_AREA = 1,

        PLAYER_CONTROL_LOST = 1,
        PLAYER_CONTROL_GAINED = 1,

        PLAYER_MOUNT_DISPLAY_CHANGED = 1,
        UPDATE_ALL_UI_WIDGETS = 1,
    }

    local kbEvents = {
        -- ACTIONBAR_SLOT_CHANGED = 1,
        ACTIONBAR_PAGE_CHANGED = 1,
        ACTIONBAR_UPDATE_STATE = 1,
        SPELLS_CHANGED = 1,
        UPDATE_SHAPESHIFT_FORM = 1,
    }

    local flashEvents = {
        -- This unregisters flash frames in SpellFlash.
        ACTIONBAR_SHOWGRID = 1,

        -- These re-register flash frames in SpellFlash (after 0.5 - 1.0s).
        ACTIONBAR_HIDEGRID = 1,
        LEARNED_SPELL_IN_TAB = 1,
        CHARACTER_POINTS_CHANGED = 1,
        ACTIVE_TALENT_GROUP_CHANGED = 1,
        UPDATE_MACROS = 1,
        VEHICLE_UPDATE = 1,
    }

    -- Opportunity for Performance Preference, maybe.
    local pulseDisplay = 0.25
    local pulseRange = TOOLTIP_UPDATE_TIME

    local LRC = LibStub( "LibRangeCheck-3.0" )
    local LSF = SpellFlashCore
    local catchFlash, lastFramesFlashed = nil, {}

    if LSF then
        hooksecurefunc( LSF, "FlashFrame", function( frame )
            local flash = frame and frame.SpellFlashCoreAddonFlashFrame

            -- We need to know what flashed so we can force it to stop flashing when the recommendation changes.
            if catchFlash and flash then
                lastFramesFlashed[ flash ] = 1
            end
        end )
    end

    local LSR = LibStub("SpellRange-1.0")
    local Glower = LibStub("LibCustomGlow-1.0")

    local function CalculateAlpha( id )
        if IsInPetBattle() or Hekili.Barber or Hekili.ClientScene or UnitHasVehicleUI( "player" ) or HasVehicleActionBar() or HasOverrideActionBar() or UnitOnTaxi( "player" ) or not Hekili:IsDisplayActive( id ) then
            return 0
        end

        local prof = Hekili.DB.profile
        local conf = prof.displays[ id ]
        local spec = state.spec.id and prof.specs[ state.spec.id ]
        local aoe  = spec and spec.aoe or 3

        local _, zoneType = IsInInstance()

        if not conf.enabled then
            return 0

        elseif id == "AOE" and Hekili:GetToggleState( "mode" ) == "reactive" and Hekili:GetNumTargets() < aoe then
            return 0

        elseif zoneType == "pvp" or zoneType == "arena" then
            if not conf.visibility.advanced then return conf.visibility.pvp.alpha end

            if conf.visibility.pvp.hideMounted and IsMounted() then return 0 end

            if conf.visibility.pvp.combatTarget > 0 and state.combat > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
                return conf.visibility.pvp.combatTarget
            elseif conf.visibility.pvp.combat > 0 and state.combat > 0 then
                return conf.visibility.pvp.combat
            elseif conf.visibility.pvp.target > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
                return conf.visibility.pvp.target
            elseif conf.visibility.pvp.always > 0 then
                return conf.visibility.pvp.always
            end

            return 0
        end

        if not conf.visibility.advanced then return conf.visibility.pve.alpha end

        if conf.visibility.pve.hideMounted and IsMounted() then return 0 end

        if conf.visibility.pve.combatTarget > 0 and state.combat > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
            return conf.visibility.pve.combatTarget
        elseif conf.visibility.pve.combat > 0 and state.combat > 0 then
            return conf.visibility.pve.combat
        elseif conf.visibility.pve.target > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then
            return conf.visibility.pve.target
        elseif conf.visibility.pve.always > 0 then
            return conf.visibility.pve.always
        end

        return 0
    end

    local numDisplays = 0

    function Hekili:CreateDisplay( id )
        local conf = rawget( self.DB.profile.displays, id )
        if not conf then return end

        if not dPool[ id ] then
            numDisplays = numDisplays + 1
            dPool[ id ] = CreateFrame( "Frame", "HekiliDisplay" .. id, UIParent )
            dPool[ id ].index = numDisplays

            Hekili:ProfileFrame( "HekiliDisplay" .. id, dPool[ id ] )
        end
        local d = dPool[ id ]

        d.id = id
        d.alpha = 0
        d.numIcons = conf.numIcons
        d.firstForce = 0
        d.threadLocked = false

        local scale = self:GetScale()
        local border = 2

        d:SetSize( scale * ( border + ( conf.primaryWidth or 50 ) ), scale * ( border + ( conf.primaryHeight or 50 ) ) )
        --[[ d:SetIgnoreParentScale( true )
        d:SetScale( UIParent:GetScale() ) ]]
        d:ClearAllPoints()

        d:SetPoint( "CENTER", UIParent, "CENTER", conf.x or 0, conf.y or -225 )
        d:SetParent( UIParent )

        d:SetFrameStrata( conf.frameStrata or "MEDIUM" )
        d:SetFrameLevel( conf.frameLevel or ( 10 * d.index ) )

        if not d:IsAnchoringRestricted() then
            d:SetClampedToScreen( true )
            d:EnableMouse( false )
            d:SetMovable( true )
        end

        function d:UpdateKeybindings()
            local conf = Hekili.DB.profile.displays[ self.id ]

            if conf.keybindings and conf.keybindings.enabled then
                for i, b in ipairs( self.Buttons ) do
                    local a = b.Action

                    if a then
                        b.Keybind, b.KeybindFrom = Hekili:GetBindingForAction( a, conf, i )

                        if i == 1 or conf.keybindings.queued then
                            b.Keybinding:SetText( b.Keybind )
                        else
                            b.Keybinding:SetText( nil )
                        end
                    else
                        b.Keybinding:SetText( nil )
                    end
                end
            end
        end

        function d:IsThreadLocked()
            return self.threadLocked
        end

        function d:SetThreadLocked( locked )
            self.threadLocked = locked
        end


        local RomanNumerals = {
            "I",
            "II",
            "III",
            "IV"
        }

local RomanNumerals = {
            "I",
            "II",
            "III",
            "IV"
        }

-- add for pengchao start --

-- 下面函数是自定义判断添加范例
-- spec:RegisterStateExpr( "has_focus", function ()
--     return UnitExists("focus")
-- end )

-- spec:RegisterStateExpr( "focus_alive", function ()
--     return UnitExists("focus") and UnitHealth("focus") > 0
-- end )

-- 进入绑定模式
local isAccountBindings = true -- true 表示修改账号绑定，false 表示修改角色绑定
LoadBindings(isAccountBindings and 1 or 2)
-- 移除绑定 直接传入按键名称，解除其绑定
-- SetBinding("CTRL-S") -- 静音开关
-- SetBinding("CTRL-TAB") -- 选中最近盟友
-- SetBinding("CTRL-7") -- 宠物快捷键
-- SetBinding("CTRL-8") -- 宠物快捷键
-- SetBinding("CTRL-9") -- 宠物快捷键
-- SetBinding("CTRL-0") -- 宠物快捷键
SetBinding("SHIFT-B") -- 开关背包
SetBinding("SHIFT-I") -- 开关宠物法术书
SetBinding("SHIFT-Y") -- 开关统计数据面板
SetBinding("SHIFT-M") -- 开关区域地图
SetBinding("SHIFT-SPACE") -- 开关积分窗口
SetBinding("SHIFT-R") -- 再次密语
SetBinding("SHIFT-TAB") -- 选中前一个敌人
SetBinding("SHIFT-F1") -- 选中队友宠物
SetBinding("SHIFT-F2") -- 选中队友宠物
SetBinding("SHIFT-F3") -- 选中队友宠物
SetBinding("SHIFT-F4") -- 选中队友宠物
SetBinding("SHIFT-F5") -- 选中队友宠物
SetBinding("SHIFT-V") -- 有房姓名版
SetBinding("SHIFT-T") -- 宠物攻击
SetBinding("SHIFT-C") -- 物品比较循环
-- 设置施法队列提前压入速度，50会让惩戒骑这种职业不再卡顿
-- 效果等同于游戏内输入 /console SET SpellQueueWindow 50
SetCVar("SpellQueueWindow", 50)
-- /dump GetCVar("SpellQueueWindow") 游戏内读取这个值




if (Hekili.DB.profile.toggles.pause.key == 'R') then
	SetBinding("R")
end
-- 保存更改
SaveBindings(isAccountBindings and 1 or 2)


if (not pcshowkey) then
	pcshowkey = CreateFrame("StatusBar", nil, UIParent)
	pcshowkey:SetPoint("TOPLEFT")
	pcshowkey:SetSize(1, 1)
	pcshowkey:SetStatusBarTexture("Interface/Buttons/WHITE8X8")
	pcshowkey:SetStatusBarColor(1, 1, 1)
	pcshowkey:SetMinMaxValues(0, 1)
	pcshowkey:SetValue(1)
	pcshowkey:SetFrameStrata("TOOLTIP")
	pcshowkey:SetFrameLevel(9999)
end

-- if (not pcshowkey2) then
-- 	pcshowkey2 = CreateFrame("StatusBar", nil, UIParent)
-- 	pcshowkey2:SetPoint("BOTTOMRIGHT")
-- 	pcshowkey2:SetSize(100, 10)
-- 	pcshowkey2:SetStatusBarTexture("Interface/Buttons/WHITE8X8")
-- 	pcshowkey2:SetStatusBarColor(1, 1, 1)
-- 	pcshowkey2:SetMinMaxValues(0, 100)
-- 	pcshowkey2:SetValue(100)
-- end

if (not bindKeyMap) then
	bindKeyMap = {
		["SHIFT-Y"] = {["r"] = 11, ["g"] = 107, ["b"] = 251},
		["SHIFT-U"] = {["r"] = 131, ["g"] = 109, ["b"] = 45},
		["SHIFT-I"] = {["r"] = 134, ["g"] = 175, ["b"] = 215},
		["SHIFT-O"] = {["r"] = 13, ["g"] = 185, ["b"] = 72},
		["SHIFT-P"] = {["r"] = 146, ["g"] = 216, ["b"] = 109},
		["SHIFT-H"] = {["r"] = 127, ["g"] = 191, ["b"] = 234},
		["SHIFT-J"] = {["r"] = 120, ["g"] = 150, ["b"] = 58},
		["SHIFT-K"] = {["r"] = 179, ["g"] = 50, ["b"] = 229},
		["SHIFT-L"] = {["r"] = 58, ["g"] = 40, ["b"] = 125},
		["SHIFT-B"] = {["r"] = 57, ["g"] = 138, ["b"] = 76},
		["SHIFT-N"] = {["r"] = 178, ["g"] = 180, ["b"] = 206},
		["SHIFT-M"] = {["r"] = 114, ["g"] = 206, ["b"] = 59},
		["SHIFT-F6"] = {["r"] = 75, ["g"] = 26, ["b"] = 211},
		["SHIFT-F7"] = {["r"] = 75, ["g"] = 29, ["b"] = 221},
		["SHIFT-F8"] = {["r"] = 71, ["g"] = 58, ["b"] = 215},
		["SHIFT-F9"] = {["r"] = 71, ["g"] = 78, ["b"] = 233},
		["SHIFT-F10"] = {["r"] = 42, ["g"] = 69, ["b"] = 214},
		["SHIFT-F11"] = {["r"] = 52, ["g"] = 63, ["b"] = 197},
		["Numpad0"] = {["r"] = 18, ["g"] = 55, ["b"] = 243},
		["Numpad1"] = {["r"] = 195, ["g"] = 231, ["b"] = 159},
		["Numpad2"] = {["r"] = 134, ["g"] = 85, ["b"] = 78},
		["Numpad3"] = {["r"] = 129, ["g"] = 98, ["b"] = 245},
		["Numpad4"] = {["r"] = 133, ["g"] = 114, ["b"] = 106},
		["Numpad5"] = {["r"] = 248, ["g"] = 60, ["b"] = 142},
		["Numpad6"] = {["r"] = 123, ["g"] = 152, ["b"] = 41},
		["Numpad7"] = {["r"] = 121, ["g"] = 81, ["b"] = 233},
		["Numpad8"] = {["r"] = 150, ["g"] = 135, ["b"] = 70},
		["Numpad9"] = {["r"] = 132, ["g"] = 129, ["b"] = 32},
		["SHIFT-F1"] = {["r"] = 110, ["g"] = 207, ["b"] = 151},
		["SHIFT-F2"] = {["r"] = 252, ["g"] = 123, ["b"] = 107},
		["SHIFT-F3"] = {["r"] = 132, ["g"] = 149, ["b"] = 212},
		["SHIFT-F4"] = {["r"] = 176, ["g"] = 129, ["b"] = 162},
		["SHIFT-F5"] = {["r"] = 78, ["g"] = 56, ["b"] = 231},
		["SHIFT-Z"] = {["r"] = 137, ["g"] = 41, ["b"] = 11},
		["SHIFT-X"] = {["r"] = 128, ["g"] = 131, ["b"] = 42},
		["SHIFT-C"] = {["r"] = 201, ["g"] = 187, ["b"] = 156},
		["SHIFT-7"] = {["r"] = 222, ["g"] = 33, ["b"] = 105},
		["SHIFT-8"] = {["r"] = 102, ["g"] = 223, ["b"] = 111},
		["SHIFT-9"] = {["r"] = 152, ["g"] = 73, ["b"] = 23},
		["SHIFT-0"] = {["r"] = 252, ["g"] = 173, ["b"] = 123}
	}
end

-- 根据当前职业绑定法术技能快捷键
-- true 打开按键日志
-- false 关闭按键日志

local pLog = false

if (not spellMapping) then
	spellMapping = {
		[262] = {        -- 萨满【元素】
			"天怒",
			"闪电之盾",
			"大地之盾",
			"风暴守护者",
			"自然迅捷",
			"风暴元素",
			"始源之潮",
			"升腾",
			"烈焰震击",
			"熔岩爆裂",
			"闪电箭",
			"闪电链",
			"岩浆图腾",
			"元素冲击",
			"地震术",
			"冰怒",
			"冰霜震击",
			"狂风怒号",
			"雷霆打击结界",
			"火元素",
			"先祖迅捷",
			"风剪",
			"大地震击",
			"收回图腾"
		},
		[263] = {        -- 萨满【增强】
			"风怒武器",
			"火舌武器",
			"闪电之盾",
			"始源之潮",
			"土元素",
			"风暴打击",
			"熔岩猛击",
			"毁灭闪电",
			"裂地术",
			"闪电箭",
			"火焰新星",
			"闪电链",
			"毁灭之风",
			"狂风怒号",
			"霜刃打击",
			"野性狼魂",
			"升腾",
			"冰霜震击",
			"风切",
			"涌动图腾",
			"元素冲击",
			"烈焰震击",
			"风剪",
			"始源风暴",
			"流电炽焰",
			"天怒",
			"收回图腾"
		},
		[70] = {         -- 圣骑士【惩戒】
			"处决宣判",
			"复仇之怒",
			"征伐",
			"最终清算",
			"圣光之锤",
			"神圣之锤",
			"审判官复仇",
			"圣殿骑士的裁决",
			"圣殿骑士斩击",
			"公正之剑",
			"灰炽觉醒",
			"圣洁鸣钟",
			"愤怒之锤",
			"圣殿骑士打击",
			"审判",
			"十字军打击",
			"神圣风暴",
			"灰烬觉醒",
			"最终审判",
			"复仇之盾",
			"责难"
		},
		[66] = {         -- 圣骑士【防骑】
			"荣耀圣令",
			"正义盾击",
			"圣盾术",
			"远古列王守卫",
			"复仇之怒",
			"炽热防御者",
			"圣疗术",
			"虔诚光环",
			"圣光裁决者",
			"奉献",
			"审判",
			"圣光之锤",
			"提尔之眼",
			"复仇者之盾",
			"十字军打击",
			"祝福之锤",
			"圣洁鸣钟",
			"正义之锤",
			"圣洁武器",
			"神圣壁垒",
			"愤怒之锤",
			"复仇之盾",
			"圣光壁垒",
			"光荣时刻",
			"责难",
			"戒卫"
		},
		[253] = {      -- 猎人【兽王】
			"召唤宠物 1",
			"猎人印记",
			"倒刺射击",
			"狂野怒火",
			"杀戮命令",
			"眼镜蛇射击",
			"多重射击",
			"爆炸射击",
			"凶暴野兽",
			"荒野的召唤",
			"黑蚀箭",
			"血溅十方",
			"弹幕射击",
			"夺命射击",
			"反制射击",
			"宁神射击",
			"治疗宠物"
		},
		[254] = { -- 射击？
			"召唤宠物 1",
			"猎人印记",
			"急速射击",
			"多重射击",
			"夺命射击",
			"爆炸射击",
			"乱射",
			"奥术射击",
			"齐射",
			"瞄准射击",
			"百发百中",
			"稳固射击",
			"哀恸箭",
			"弹幕射击",
			"黑蚀箭",
			"反制射击",
			"宁神射击"
		},
		[255] = {  --生存
			"召唤宠物 1",
			"猎人印记",
			"野火炸弹",
			"雄鹰之怒",
			"爆炸射击",
			"屠戮",
			"鱼叉猛刺",
			"猛禽一击",
			"锐矛之锋",
			"协同进攻",
			"杀戮命令",
			"夺命射击",
			"猫鼬撕咬",
			"侧翼打击",
			"压制",
			"宁神射击",
			"雄鹰守护",
			"治疗宠物"
		},
		[269] = { -- 踏风
			"猛虎掌",
			"白虎下凡",
			"风火雷电",
			"业报之触",
			"轮回之触",
			"神鹤引项踢",
			"天神御身",
			"旭日东升踢",
			"升龙霸",
			"幻灭踢",
			"风领主之击",
			"碎玉闪电",
			"怒雷破",
			"碧火踏",
			"妖魂踏",
			"真气爆裂",
			"切喉手",
			"切削之风"
		},
		[64] = {  --冰法
			"奥术智慧",
			"镜像",
			"寒冰箭",
			"冰冷血脉",
			"冰风暴",
			"彗星风暴",
			"冰川尖刺",
			"寒冰宝珠",
			"镜像",
			"冰枪术",
			"暴风雪",
			"冰锥术",
			"冰冻术",
			"寒冰新星",
			"变易幻能",
			"冰霜射线",
			"霜火之箭",
			"法术反制"
		},
		[63] = {  --火法
			"奥术智慧",
			"镜像",
			"不死鸟之焰",
			"流星",
			"燃烧",
			"烈焰风暴",
			"火焰冲击",
			"灼烧",
			"霜火之箭",
			"变易幻能",
			"火球术",
			"炎爆术",
			"寒冰新星",
			"法术反制"
		},
		[62] = { --奥法
			"奥术智慧",
			"镜像",
			"唤醒",
			"奥术飞弹",
			"奥术涌动",
			"大法师之触",
			"奥术冲击",
			"气定神闲",
			"奥术弹幕",
			"奥术宝珠",
			"变易幻能",
			"魔爆术",
			"法术反制"
		},
		[268] = { -- 武僧 酒仙
			"散魔功",
			"活血术",
			"活血酒",
			"天神酒",
			"躯不坏",
			"壮胆酒",
			"轮回之触",
			"移花接木",
			"玄牛酒",
			"幻灭踢",
			"真气爆裂",
			"精序兵戈",
			"旭日东升踢",
			"猛虎掌",
			"醉酿投",
			"神鹤引项踢",
			"火焰之息",
			"爆炸酒桶",
			"碧玉疾风",
			"玄牛下凡",
			"切喉手",
			"天神灌注"
		},
		[258] = { -- 牧师 暗牧
			"黑暗升华",
			"虚空冲击",
			"真言术：韧",
			"暗影形态",
			"暗影魔",
			"摧心魔",
			"吸血鬼之触",
			"心灵震爆",
			"能量灌注",
			"噬灵疫病",
			"虚空洪流",
			"精神鞭笞",
			"精神鞭笞：狂",
			"光晕",
			"暗影冲撞",
			"虚空爆发",
			"虚空箭",
			"暗言术：痛",
			"暗言术：灭",
			"渐隐术",
			"神圣新星",
			"消散",
			"奥术脉冲",
			"绝望祷言",
			"沉默",
			"心灵尖刺",
			"心灵尖刺：狂",
			"虚空幽灵",
			"虚空齐射"
		},
		[71] = { -- 战士 武器
			"普生堡垒之矛",
			"顺劈斩",
			"雷霆一击",
			"雷鸣之吼",
			"天神下凡",
			"横扫攻击",
			"破坏者",
			"灭战者",
			"勇士之矛",
			"巨人打击",
			"剑刃风暴",
			"崩摧",
			"压制",
			"致死打击",
			"碎颅打击",
			"斩杀",
			"旋风斩",
			"冲锋",
			"撕裂",
			"猛击",
			"战斗怒吼",
			"战斗姿态",
			"风暴之锤",
			"拳击"
		},
		[72] = { -- 战士 狂暴
			"冲锋",
			"应用飞跃",
			"战斗怒吼",
			"狂暴姿态",
			"鲁莽",
			"天神下凡",
			"雷鸣之吼",
			"普生堡垒之矛",
			"奥丁之怒",
			"旋风斩",
			"斩杀",
			"暴怒",
			"剑刃风暴",
			"强攻",
			"嗜血",
			"怒击",
			"风暴之锤",
			"猛击",
			"浴血奋战",
			"碎甲猛击",
			"雷霆轰击",
			"破坏者",
			"雷霆一击",
			"勇士之矛",
			"拳击"
		},
		[73] = { -- 战士 防御
			"雷霆轰击",
			"雷霆一击",
			"复仇",
			"盾牌猛击",
			"冲锋",
			"天神下凡",
			"法术格挡",
			"盾墙",
			"无视苦痛",
			"破釜沉舟",
			"集结呐喊",
			"乘胜追击",
			"胜利在望",
			"破坏者",
			"挫志怒吼",
			"崩摧",
			"雷鸣之吼",
			"盾牌冲锋",
			"盾牌格挡",
			"斩杀",
			"毁灭打击",
			"战斗姿态",
			"勇士之矛",
			"勇士之矛",
			"拳击",
			"战斗怒吼"
		},
		[265] = { -- 术士 痛苦		
			"召唤萨亚德",
			"牺牲魔典",
			"诡异魅影",
			"鬼影缠身",
			"痛楚",
			"召唤黑眼",
			"邪恶污染",
			"痛苦无常",
			"灵魂腐化",
			"灾难狂欢",
			"腐蚀之种",
			"腐蚀术",
			"吸取灵魂",
			"枯萎",
			"怨毒",
			"湮灭",
			"法术封锁",
			"邪能统御",
			"暗影箭",
			"召唤地狱猎犬"
		},
		[266] = { --术士 恶魔学士
			"能量虹吸",
			"内爆",
			"召唤恶魔暴君",
			"恶魔力量",
			"古尔丹之手",
			"召唤灼焦恶犬",
			"召唤恐惧猎犬",
			"恶魔之箭",
			"魔典：恶魔卫士",
			"暗影箭",
			"狱火箭",
			"陨灭",
			"召唤恶魔卫士",
			"灾怨轰炸",
			"断魂斧",
			"法术封锁",
			"邪能统御",
			"召唤阴暗恶犬",
			"召唤地狱猎犬"
		},
		[267] = { --术士 毁灭!！
			"召唤萨亚德",
			"烧尽",
			"燃烧",
			"混乱之箭",
			"暗影灼烧",
			"火焰之雨",
			"引导恶魔之火",
			"怨毒",
			"召唤地狱火",
			"邪能统御",
			"枯萎",
			"灵魂之火",
			"次元裂隙",
			"献祭",
			"陨灭",
			"狱火箭",
			"浩劫",
			"大灾变",
			"法术封锁",
			"召唤地狱猎犬"
		},
		[1467] = { -- 湮灭
			"裂解",
			"狂龙之怒",
			"永恒之涌",
			"碎裂星辰",
			"葬火",
			"活化烈焰",
			"回归",
			"深呼吸",
			"扭转天平",
			"火焰吐息",
			"碧蓝打击",
			"悬空",
			"焚身",
			"火焰风暴",
			"镇压"
		},
		[1473] = { -- 增辉
			"活化烈焰",
			"炽火龙鳞",
			"先知先觉",
			"黑檀之力",
			"扭转天平",
			"喷发",
			"火焰吐息",
			"地壳激变",
			"亘古吐息",
			"镇压"
		},
		[250] = { --血DK
			"亡者复生",
			"死神的抚摩",
			"死神印记",
			"灵界打击",
			"血液沸腾",
			"吞噬",
			"憎恶附肢",
			"精髓分裂",
			"符文刃舞",
			"白骨风暴",
			"墓石",
			"心脏打击",
			"吸血鬼打击",
			"灵魂收割",
			"枯萎凋零",
			"冰封之韧",
			"符文分流",
			"吸血鬼之血",
			"反魔法护罩",
			"心灵冰冻",
			"牺牲契约",
			"饮血者"
		},
		[251] = { --冰DK
			"憎恶附肢",
			"冷酷严冬",
			"枯萎凋零",
			"凛风冲击",
			"冰龙吐息",
			"冰霜之柱",
			"死神印记",
			"亡者复生",
			"湮灭",
			"冰霜打击",
			"符文武器增效",
			"冰霜巨龙之怒",
			"冰霜之镰",
			"寒冬号角",
			"冰川突进",
			"灵魂收割",
			"寒冰联结",
			"反魔法护罩",
			"心灵冰冻"
		},
		[252] = {  --邪DK
			"亡者复生",
			"培育憎恶",
			"黑暗突变",
			"邪恶突袭",
			"憎恶附肢",
			"扩散",
			"爆发",
			"天灾打击",
			"亵渎",
			"脓疮打击",
			"脓疮毒镰",
			"凋零缠绕",
			"召唤石像鬼",
			"吸血鬼打击",
			"天启",	
			"邪恶蔓延",
			"反魔法护罩",
			"心灵冰冻",
			"灵魂收割",
			"亡者大军",
			"枯萎凋零",
			"暗影之爪",
			"灵魂军团"
		},
		[577] = {  --浩劫
			"烈焰咒符",
			"献祭光环",
			"邪能冲撞",
			"恶魔追击",
			"眼棱",
			"死亡横扫",
			"恶魔变形",
			"复仇回避",
			"刃舞",
			"混乱打击",
			"吞噬之焰",
			"毁灭",
			"战刃风暴",
			"邪能之刃",
			"投掷利刃",
			"精华破碎",
			"末日咒符",
			"深渊凝视",
			"收割者战刃",
			"怨念咒符",
			"邪能弹幕",
			"瓦解",
			"恶魔之咬"
		},
		[581] = {  --复仇
			"烈焰咒符",
			"恶魔尖刺",
			"地狱火撞击",
			"献祭光环",
			"破裂",
			"烈火烙印",
			"邪能毁灭",
			"灵魂爆裂",
			"灵魂割裂",
			"灵魂裂劈",
			"恶魔变形",
			"怨念咒符",
			"恶魔追击",
			"邪能之刃",
			"邪能荒芜",
			"收割者战刃",
			"幽魂炸弹",
			"灵魂切削",
			"噬众",
			"瓦解"
		},
		[259] = { --奇袭
			"夺命药膏",
			"增效药膏",
			"萎缩药膏",
			"减速药膏",
			"潜行",
			"锁喉",
			"刀扇",
			"毁伤",
			"猩红风暴",
			"割裂",
			"毒伤",
			"毒刃",
			"切割",
			"君王之灾",
			"死亡印记",
			"消失",
			"伏击",
			"菊花茶",
			"脚踢",
			"迟钝药膏",
			"冷血"
		},
		[260] = {-- 盗贼 狂徒
			"萎缩药膏",
			"速效药膏",
			"潜行",
			"冲动",
			"剑刃乱舞",
			"命运骨骰",
			"正中眉心",
			"鬼魅攻击",
			"消失",
			"影袭",
			"斩击",
			"手枪射击",
			"时运继延",
			"冷血",
			"菊花茶",
			"影舞步",
			"刀锋冲刺",
			"致命一击",
			"脚踢",
			"伏击",
			"迟钝药膏"
		},
		[261] = {-- 盗贼 敏锐
			"速效药膏",
			"迟钝药膏",
			"萎缩药膏",
			"潜行",
			"暗影打击",
			"狂热鞭笞",
			"消失",
			"刺骨",
			"影分身",
			"袖剑风暴",
			"袖剑旋风",
			"背刺",
			"致命一击",
			"割裂",
			"死亡符记",
			"暗影之舞",
			"暗影之刃",
			"幽暗之刃",
			"黑火药",
			"菊花茶",
			"冷血",
			"赤喉之咬",
			"脚踢"
		},
		[102] = { --德鲁伊 平衡德
			"枭兽形态",
			"愤怒",
			"艾露恩的战士",
			"阳炎术",
			"月火术",
			"化身：艾露恩之眷",
			"星涌术",
			"星火术",
			"自然的守护",
			"野性印记",
			"半月",
			"自然之力",
			"星辰坠落",
			"新月",
			"满月",
			"星辰耀斑",
			"艾露恩之怒",
			"万灵之召",
			"超凡之盟",
			"野性蘑菇",
			"日光术",
			"甘霖",
			"安抚"
		},
		[103] = { --德鲁伊 野性
			"斜掠",
			"狂暴回复",
			"痛击",
			"割裂",
			"割碎",
			"迎头痛击",
			"野性冲锋",
			"猛虎冲刺",
			"安抚",
			"甘霖",
			"蛮力猛击",
			"自然的守护",
			"野性之心",
			"猛虎之怒",
			"原始之怒",
			"生存本能",
			"狂暴",
			"野蛮挥砍",
			"激变蜂群",
			"化身：阿莎曼之灵",
			"万灵之召",
			"野性狂乱",
			"树皮术",
			"愈合",
			"凶猛撕咬",
			"愤怒",
			"潜行",
			"撕碎",
			"猎豹形态",
			"野性印记",
			"月火术"
		},
		[104] = { --德鲁伊 守护
			"熊形态",
			"野性印记",
			"野性之心",
			"月火术",
			"痛击",
			"明月普照",
			"化身：乌索克的守护者",
			"重殴",
			"沉睡者之怒",
			"铁鬃",
			"裂伤",
			"横扫",
			"树皮术",
			"甘霖",
			"狂暴回复",
			"生存本能",
			"摧折",
			"粉碎",
			"迎头痛击",
			"甘霖",
			"安抚",
			"狂暴"
		}
	}
end

-- 种族技能
if (not raceMapping) then
	raceMapping = {
		["BloodElf"] = "奥术洪流(种族特长)", --"血精灵",
		["Vulpera"] = "袋里乾坤(种族特长)", --"狐人"
		["Nightborne"] = "奥术脉冲(种族特长)", -- "夜之子"
		["MagharOrc"] = "先祖召唤(种族特长)", -- "玛格汉兽人"
		["Troll"] = "狂暴(种族特长)", -- 巨魔
		["LightforgedDraenei"] = "圣光裁决者(种族特长)", -- 光柱德莱尼
		["Orc"] = "血性狂怒(种族特长)", -- 兽人
		["Goblin"] = "火箭弹幕(种族特长)", -- 地精
		["Dwarf"] = "石像形态(种族特长)", -- 矮人
		["Gnome"] = "逃命专家(种族特长)", -- 侏儒
		["Draenei"] = "纳鲁的赐福(种族特长)", -- 德莱尼
		["Worgen"] = "疾步夜行(种族特长)", -- 狼人
		["Pandaren"] = "震山掌(种族特长)", -- 熊猫人
		["DarkIronDwarf"] = "烈焰之血(种族特长)" -- 黑铁矮人
	}
end

-- 鼠标指向型的饰品
if (not useTrinketMap) then
	useTrinketMap = {
		["爆裂圣光碎片"] = true,
		["诺格弗格至尊豪华版"] = true
	}
end

if (not bkeyMapping) then
	bkeyMapping = {
		[1] = "SHIFT-Y",
		[2] = "SHIFT-U",
		[3] = "SHIFT-I",
		[4] = "SHIFT-O",
		[5] = "SHIFT-P",
		[6] = "SHIFT-H",
		[7] = "SHIFT-J",
		[8] = "SHIFT-K",
		[9] = "SHIFT-L",
		[10] = "SHIFT-B",
		[11] = "SHIFT-N",
		[12] = "SHIFT-M",
		[13] = "SHIFT-F6",
		[14] = "SHIFT-F7",
		[15] = "SHIFT-F8",
		[16] = "SHIFT-F9",
		[17] = "SHIFT-F10",
		[18] = "SHIFT-F11",
		[19] = "Numpad0",
		[20] = "Numpad1",
		[21] = "Numpad2",
		[22] = "Numpad3",
		[23] = "Numpad4",
		[24] = "Numpad5",
		[25] = "Numpad6",
		[26] = "SHIFT-F1",
		[27] = "SHIFT-F2",
		[28] = "SHIFT-F3",
		[29] = "SHIFT-F4",
		[30] = "SHIFT-F5",
		[31] = "SHIFT-Z",
		[32] = "SHIFT-X",
		[33] = "SHIFT-C",
		[34] = "SHIFT-7",
		[35] = "SHIFT-8",
		[36] = "SHIFT-9",
		[37] = "SHIFT-0",
		[38] = "Numpad7", -- 种族技能
		[39] = "Numpad8", -- 饰品位置
		[40] = "Numpad9" -- 饰品位置
	}
end

if (not tempKey) then
	tempKey = {}
end

function table.contains(table, value)
    for _, v in ipairs(table) do
        if v == value then
            return true
        end
    end
    return false
end

local addKey = "SHIFT-"

local function autoKeybind()

	local specIndex = GetSpecialization()  -- 获取玩家当前专精的索引（从1开始）

	if specIndex then
	    local specID, specName, _, specIcon = GetSpecializationInfo(specIndex)
	    -- print("当前专精：" .. specID)  -- 打印专精名称
		-- 打印专精 ID
		if (specID) then
			local splms = spellMapping[specID]
			if (splms) then
				
				-- 指向性法术直接在自己脚下施放
				-- local focusToFeedSpells = {
				-- 	"最终清算", -- 惩戒 最终清算
				-- 	"涌动图腾",  -- 萨满 涌动图腾
				-- 	"爆炸酒桶", -- 酒仙 爆炸酒桶
				-- 	"破坏者", -- 战士 破坏者
				-- 	"勇士之矛", -- 战士 勇士之矛
				-- 	"枯萎凋零", --冰DK 枯萎凋零
				-- 	"亵渎", --邪DK 亵渎
				-- 	"烈焰咒符", --浩劫 烈焰咒符
				-- 	"恶魔变形", --浩劫 恶魔变形
				-- 	"末日咒符",  --浩劫 末日咒符
				-- 	"怨念咒符", --浩劫 怨念咒符
				-- 	"地狱火撞击" -- 复仇 地狱火撞击
				-- }
				
				local focusToFeedSpells = {
					["最终清算"] = true, -- 惩戒 最终清算
					["涌动图腾"] = true,  -- 萨满 涌动图腾
					["爆炸酒桶"] = true, -- 酒仙 爆炸酒桶
					["破坏者"] = true, -- 战士 破坏者
					["勇士之矛"] = true, -- 战士 勇士之矛
					["枯萎凋零"] = true, --冰DK 枯萎凋零
					["亵渎"] = true, --邪DK 亵渎
					["烈焰咒符"] = true, --浩劫 烈焰咒符
					["恶魔变形"] = true, --浩劫 恶魔变形
					["末日咒符"] = true,  --浩劫 末日咒符
					["怨念咒符"] = true, --浩劫 怨念咒符
					["地狱火撞击"] = true -- 复仇 地狱火撞击
				}
				
				-- 指向性法术鼠标位置施放
				local focusToMouse = {}
				-- 勾选自动释放在鼠标位置才生效
				if (Hekili.DB.profile.toggles.auto_spell_to_mouse_here.value) then
					focusToMouse = {
						["岩浆图腾"] = true, -- 元素萨满 岩浆图腾
						["地震术"] = true, -- 元素萨满 地震术
						["乱射"] = true, -- 射击猎人 乱射
						["烈焰风暴"] = true, --火法 烈焰风暴
						["暴风雪"] = true, -- 冰法 暴风雪
						["冰冻术"] = true, -- 冰法 冰冻术（宠物技能）
						["流星"] = true, -- 火法 流星
						["邪恶污染"] = true, -- 痛苦 邪恶污染
						["断魂斧"] = true, -- 恶魔学识 断魂斧
						["火焰之雨"] = true, --毁灭 火焰之雨
						["召唤地狱火"] = true, --毁灭 召唤地狱火(守卫)
						["大灾变"] = true, --毁灭 大灾变
						["自然之力"] = true, --平衡德 自然之力
						["暗影冲撞"] = true, -- 暗牧 暗影冲
						["火焰风暴"] = true -- 湮灭 火焰风暴
					}
				end
			
				for index, spellName in ipairs(spellMapping[specID]) do
			
					-- maxIndex = index
					local bkey = bkeyMapping[index]
					local auto_key = _G[bkey]
					if (not auto_key) then
						auto_key = CreateFrame("Button", bkey, UIParent, "SecureActionButtonTemplate")
						auto_key:RegisterForClicks("AnyUp", "AnyDown")
						auto_key:SetAttribute("type", "macro")
					end
			
			
			
					if ("圣光之锤" == spellName) then 
						-- 惩戒大锤特殊判定
						if (specID == 66) then
							-- 防骑
							auto_key:SetAttribute("macrotext", "/cast [nodead] 提尔之眼")
						else
							-- 惩戒
							auto_key:SetAttribute("macrotext", "/cast [nodead] 灰烬觉醒")
						end
					elseif ("倒刺射击" == spellName) then
						-- 兽王
						if (specID == 253) then
							auto_key:SetAttribute("macrotext", "/cast [@mouseover,exists,nodead] 倒刺射击; [@target,nodead] 倒刺射击")
						end
					elseif ("灵魂割裂" == spellName) then
						-- 复仇
						if (specID == 581) then
							auto_key:SetAttribute("macrotext", "/cast [nodead] 灵魂裂劈")
						end
					elseif ("虚空齐射" == spellName) then
						-- 暗牧
						if (specID == 258) then
							-- 增强闪电箭特效处理
							auto_key:SetAttribute("macrotext", "/cast [nodead] 虚空洪流")
						end
					elseif ("圣洁武器" == spellName) then
						-- 防骑
						if (specID == 66) then
							-- 增强闪电箭特效处理
							auto_key:SetAttribute("macrotext", "/cast [nodead] 圣洁武器 /cast [nodead] 神圣壁垒")
						end
					elseif ("神圣壁垒" == spellName) then
						-- 防骑
						if (specID == 66) then
							-- 增强闪电箭特效处理
							auto_key:SetAttribute("macrotext", "/cast [nodead] 圣洁武器 /cast [nodead] 神圣壁垒")
						end
					elseif ("回归" == spellName) then
						if (specID == 1467) then
							-- 增强闪电箭特效处理
							auto_key:SetAttribute("macrotext", "/cast [nodead] 深呼吸")
						end
					elseif ("狂风怒号" == spellName) then
						-- 增强闪电箭特效处理
						auto_key:SetAttribute("macrotext", "/cast [nodead] 闪电箭")
					elseif ("风火雷电：锁定" == spellName) then
						-- 踏风 烽火雷电
						auto_key:SetAttribute("macrotext", "/cast [nodead] 风火雷电")
					elseif ("雷霆轰击" == spellName) then
						-- 战士 雷霆轰击
						auto_key:SetAttribute("macrotext", "/cast [nodead] 雷霆一击")
					elseif ("脓疮毒镰" == spellName) then
						-- 邪DK心脏打击特效处理
						auto_key:SetAttribute("macrotext", "/cast [nodead] 脓疮打击")
					elseif ("吸血鬼打击" == spellName) then
						-- 邪DK天灾打击特效处理
						if (specID == 252) then
							-- 邪DK
							auto_key:SetAttribute("macrotext", "/cast [nodead] 天灾打击")
						else
							-- 血DK
							auto_key:SetAttribute("macrotext", "/cast [nodead] 心脏打击")
						end
					elseif ("致命一击" == spellName) then
						-- 盗贼狂徒伏击特效处理
						if (specID == 260) then
							-- 盗贼 狂徒
							-- auto_key:SetAttribute("macrotext", "/cast [nodead] 伏击")
							auto_key:SetAttribute("macrotext", "/cast [nodead] 斩击")
						else
							--  盗贼 敏锐
							auto_key:SetAttribute("macrotext", "/cast [nodead] 刺骨")
						end
						
					elseif (focusToFeedSpells[spellName]) then
						auto_key:SetAttribute("macrotext", "/cast [@player] "..spellName)
					elseif (focusToMouse[spellName]) then
						auto_key:SetAttribute("macrotext", "/cast [@cursor] "..spellName)
					else
						auto_key:SetAttribute("macrotext", "/cast [nodead] "..spellName)
						-- print(bkey.."创建按键绑定成功"..spellName)
					end
					if InCombatLockdown() ~= true then
						SetBinding(bkey, nil)
						SetOverrideBindingClick(auto_key, true, bkey, auto_key:GetName())
						tempKey[spellName] = bkey
						
					else
						print("生成一键宏失败, 请脱战之后 /RL  一下")
					end
				end
			else
				if (specName) then
					print("|c21C80000抱歉|r|cFF37EB00"..specName.."|r|c21C80000暂不支持，详细咨询请联系客服\r\n")
				end
				-- print("专精 ID：" .. specID)
			end
		end
	else
	    print("玩家没有选择专精或尚未登录")
	end
end

local function raceBind(bkey)
	local _, raceID = UnitRace("player")
	if (pLog) then
		print(raceID)
	end
	local spellName = raceMapping[raceID]
	if (spellName) then
		local auto_key = _G[bkey]
		if (not auto_key) then
			auto_key = CreateFrame("Button", bkey, UIParent, "SecureActionButtonTemplate")
			auto_key:RegisterForClicks("AnyUp", "AnyDown")
			auto_key:SetAttribute("type", "macro")
		end
		auto_key:SetAttribute("macrotext", "/cast [nodead] "..spellName)
		if InCombatLockdown() ~= true then
			SetBinding(bkey, nil)
			SetOverrideBindingClick(auto_key, true, bkey, auto_key:GetName())
			tempKey[spellName] = bkey
		else
			print("生成一键宏失败, 请脱战之后 /RL  一下")
		end
	end
end

local function trinketBind(index, bkey)

	local trinket = GetInventoryItemID("player", index)

	if trinket then
		local trinketName = GetItemInfo(trinket)
		local auto_key = _G[bkey]
		if (not auto_key) then
			auto_key = CreateFrame("Button", bkey, UIParent, "SecureActionButtonTemplate")
			auto_key:RegisterForClicks("AnyUp", "AnyDown")
			auto_key:SetAttribute("type", "macro")
		end
		if (useTrinketMap[trinketName]) then
			auto_key:SetAttribute("macrotext", "/cast [@cursor] "..trinketName)
		else
			auto_key:SetAttribute("macrotext", "/cast [nodead] "..trinketName)
		end
		
		if InCombatLockdown() ~= true then
			SetBinding(bkey, nil)
			SetOverrideBindingClick(auto_key, true, bkey, auto_key:GetName())
			tempKey[trinket] = bkey
			-- print("创建按键绑定成功")
			-- print("/cast "..trinketName.." -- ALT-"..bkey)
		else
			print("生成一键宏失败, 请脱战之后 /RL  一下")
		end
	end
end

autoKeybind()
trinketBind(16, "0")
trinketBind(13, "Numpad8")
trinketBind(14, "Numpad9")
raceBind("Numpad7")

local ftrinket = CreateFrame("Frame")
-- 饰品更换
local function ftrinketOnEvent(self, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
    if event == "PLAYER_EQUIPMENT_CHANGED" then
        local slotId = arg1
        -- local itemId = arg2
        if slotId == 16 or slotId == 13 or slotId == 14 then
			trinketBind(16, "0")
            trinketBind(13, "Numpad8")
            trinketBind(14, "Numpad9")
			if (pLog) then
				print('重新绑定了饰品')
			end
        end
    end
end
ftrinket:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
ftrinket:SetScript("OnEvent", ftrinketOnEvent)
-- 专精切换
local function onSpecializationChange(self, event, ...)
	if (pLog) then
		print('重新绑定所有按键')
	end
	autoKeybind()
end
local zhuanjing = CreateFrame("Frame")
zhuanjing:RegisterEvent("PLAYER_TALENT_UPDATE")
zhuanjing:SetScript("OnEvent", onSpecializationChange)

local function changeToColor(nr, ng, nb)
	if (pcshowkey) then
		pcshowkey:SetStatusBarColor(nr, ng, nb)
	end
end

-- add for pengchao end --

        function d:OnUpdate( elapsed )
            if not self.Recommendations or not Hekili.PLAYER_ENTERING_WORLD or self:IsThreadLocked() then
                return
            end

            local init = debugprofilestop()

            local profile = Hekili.DB.profile
            local conf = profile.displays[ self.id ]

            self.timer = ( self.timer or 0 ) - elapsed
            self.alphaCheck = self.alphaCheck - elapsed

            if alphaCheck then
                self:UpdateAlpha()
            end

            if not self.id == "Primary" and not ( self.Buttons[ 1 ] and self.Buttons[ 1 ].Action ) and not ( self.HasRecommendations or not self.NewRecommendations ) then
                return
            end

            if Hekili.Pause and not self.paused then
                self.Buttons[ 1 ].Overlay:Show()
                self.paused = true
            elseif not Hekili.Pause and self.paused then
                self.Buttons[ 1 ].Overlay:Hide()
                self.paused = false
            end

            local fullUpdate = self.NewRecommendations or self.timer < 0
            if not fullUpdate then return end

            local madeUpdate = false

            self.timer = pulseDisplay

            local now = GetTime()

            if self.NewRecommendations then
                self.NewRecommendations = nil
                madeUpdate = true

                local alpha = self.alpha
                local options = Hekili:GetActiveSpecOption( "abilities" )

                if self.HasRecommendations and self.RecommendationsStr and self.RecommendationsStr:len() == 0 then
                    for i, b in ipairs( self.Buttons ) do b:Hide() end
                    self.HasRecommendations = false

                elseif self:IsThreadLocked() then
                    self.HasRecommendations = true

                else
                    self.HasRecommendations = true

                    for i, b in ipairs( self.Buttons ) do
                        b.Recommendation = self.Recommendations[ i ]

                        local action = b.Recommendation.actionName
                        local caption = b.Recommendation.caption
                        local indicator = b.Recommendation.indicator
                        local keybind = b.Recommendation.keybind
                        local exact_time = b.Recommendation.exact_time

                        local ability = class.abilities[ action ]

                        if ability and ability.id < 0 and ability.id > -100 and action ~= "cancel_buff" or exact_time == nil then ability = nil end

                        if ability then
                            if ( conf.flash.enabled and conf.flash.suppress ) then b:Hide()
                            else b:Show() end

                            --[[ if i == 1 then
                                -- print( "Changing", GetTime() )
                            end ]]

                            local image -- texture to be shown on the button for the current action

                            if ability.item then
                                image = b.Recommendation.texture or ability.texture or select( 5, GetItemInfoInstant( ability.item ) )
                            else
                                local override = options and rawget( options, action )
                                image = override and override.icon or b.Recommendation.texture or ability.texture or GetSpellTexture( ability.id )
                            end

                            if action ~= b.lastAction or image ~= b.lastImage or self.NewRecommendations or not b.Image then
                                b.Image = image
                                b.Texture:SetTexture( b.Image )
                                b.Texture:SetTexCoord( unpack( b.texCoords ) )
                                b.lastAction = action
                                b.lastImage = image
                            end

                            b.Texture:Show()

                            if i == 1 then
                                if conf.glow.highlight then
                                    local id = ability.item or ability.id
                                    local isItem = ability.item ~= nil

                                    if id and ( isItem and IsCurrentItem( id ) or IsCurrentSpell( id ) ) and exact_time > GetTime() then
                                        b.Highlight:Show()
                                    else
                                        b.Highlight:Hide()
                                    end

                                elseif b.Highlight:IsShown() then
                                    b.Highlight:Hide()
                                end
                            end


                            if ability.empowered then
                                b.EmpowerLevel:SetText( RomanNumerals[ b.Recommendation.empower_to or ability.empowerment_default or state.max_empower ] )
                            else
                                b.EmpowerLevel:SetText( nil )
                            end

                            if conf.indicators.enabled and indicator then
                                if indicator == "cycle" then
                                    b.Icon:SetTexture("Interface\\Addons\\Hekili\\Textures\\Cycle")
                                end
                                if indicator == "cancel" then
                                    b.Icon:SetTexture("Interface\\Addons\\Hekili\\Textures\\Cancel")
                                end
                                b.Icon:Show()
                            else
                                b.Icon:Hide()
                            end

                            if ( caption and conf.captions.enabled or ability.caption and not ability.empowered ) and ( i == 1 or conf.captions.queued ) then
                                b.Caption:SetText( caption )
                            else
                                b.Caption:SetText(nil)
                            end

                            if conf.keybindings.enabled and ( i == 1 or conf.keybindings.queued ) then
                                b.Keybinding:SetText( keybind )
								
								-- add for pengchao start --
								
								if (i == 1 and not self.paused) then
								
									local spID
									local bindKeyN
									if (ability.item) then
										spID = ability.item
										bindKeyN = tempKey[spID]
									else
										spID = ability.id
										if (string.find(LSF.SpellName(spID), "种族特长")) then
											bindKeyN = tempKey[LSF.SpellName(spID)]
										else
											bindKeyN = tempKey[string.gsub(LSF.SpellName(spID), "%b()", "")]
										end
									end
									
									local start, duration, enable = GetSpellCooldown(spID)
									-- 获取cd冷却时间
									local currentTime = GetTime()  -- 获取当前时间（秒）
									local remainingTime = (start + duration) - currentTime
									local showTime = remainingTime > 0 and remainingTime or 0
									-- print(Hekili.DB.profile.toggles.spell_speed.spell_speedThreshold >= showTime)
									if conf.range.enabled and UnitCanAttack( "player", "target" ) and not b.outOfRange and showTime <= Hekili.DB.profile.toggles.spell_speed.spell_speedThreshold then
										
										local lname, _, _, _, _, _, lspellID = UnitChannelInfo("player")
										if lname then
											-- 玩家正在施放引导技能
											-- print("正在引导技能: " .. lname)
											-- print("法术 ID: " .. lspellID)
											changeToColor(1, 1, 1)
										else
											-- 玩家没有施放引导技能
											-- print("没有施放引导技能")
											if (bindKeyN) then
												local colorMap = bindKeyMap[bindKeyN]
												local nr = colorMap["r"] / 255.0
												local ng = colorMap["g"] / 255.0
												local nb = colorMap["b"] / 255.0
												if UnitCanAttack("player", "target") and not UnitIsDead("target") then
												    
													if (not ChatFrame1EditBox:IsShown()) then
														if (not SpellIsTargeting()) then
														
															if (pLog) then
																print("目标可攻击")
																if (ability.item) then
																	print("绑定按键"..bindKeyN.."技能名字"..LSF.ItemName( ability.item ))
																else
																	print("绑定按键"..bindKeyN.."技能名字"..LSF.SpellName(spID))
																end
															end
															changeToColor(nr, ng, nb)
														else
															if (pLog) then
																print("选取技能状态，暂不攻击")
																changeToColor(1, 1, 1)
															end
														end
														
													else
														if (pLog) then
															print("弹出了输入框，不攻击")
														end
														changeToColor(1, 1, 1)
													end
													
												else
													if (pLog) then
														print("目标不可攻击")
													end
													changeToColor(1, 1, 1)
												end
												-- changeToColor(nr, ng, nb)
												-- if (pLog) then
												-- 	print("绑定按键"..bindKeyN)
												-- end
											else
												if (pLog) then
													print("法术ID:"..spID.." 法术名:"..string.gsub(LSF.SpellName(spID), "%b()", ""))
												end
											end
											
										end
									else
										changeToColor(1, 1, 1)
									end
								elseif self.paused then
									changeToColor(1, 1, 1)
								end
								
								-- add for pengchao end --
								
                            else
                                b.Keybinding:SetText(nil)
                            end

                            if conf.glow.enabled and ( i == 1 or conf.glow.queued ) and IsSpellOverlayed( ability.id ) then
                                b.glowColor = b.glowColor or {}

                                if conf.glow.coloring == "class" then
                                    b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA()
                                elseif conf.glow.coloring == "custom" then
                                    b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color)
                                else
                                    b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1
                                end

                                if conf.glow.mode == "default" then
                                    Glower.ButtonGlow_Start( b, b.glowColor )
                                    b.glowStop = Glower.ButtonGlow_Stop
                                elseif conf.glow.mode == "autocast" then
                                    Glower.AutoCastGlow_Start( b, b.glowColor )
                                    b.glowStop = Glower.AutoCastGlow_Stop
                                elseif conf.glow.mode == "pixel" then
                                    Glower.PixelGlow_Start( b, b.glowColor )
                                    b.glowStop = Glower.PixelGlow_Stop
                                end

                                b.glowing = true
                            elseif b.glowing then
                                if b.glowStop then b:glowStop() end
                                b.glowing = false
                            end

                        else
                            b:Hide()
                        end

                        b.Action = action
                        b.Text = caption
                        b.Indicator = indicator
                        b.Keybind = keybind
                        b.Ability = ability
                        b.ExactTime = exact_time
                    end

                    self:RefreshCooldowns( "RECS_UPDATED" )
                end
            end

            local postRecs = debugprofilestop()

            if self.HasRecommendations then
                if fullUpdate and conf.glow.enabled then
                    madeUpdate = true

                    for i, b in ipairs( self.Buttons ) do
                        if not b.Action then break end

                        local a = b.Ability

                        if i == 1 or conf.glow.queued then
                            local glowing = a.id > 0 and IsSpellOverlayed( a.id )

                            if glowing and not b.glowing then
                                b.glowColor = b.glowColor or {}

                                if conf.glow.coloring == "class" then
                                    b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA()
                                elseif conf.glow.coloring == "custom" then
                                    b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color)
                                else
                                    b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1
                                end

                                if conf.glow.mode == "default" then
                                    Glower.ButtonGlow_Start( b, b.glowColor )
                                    b.glowStop = Glower.ButtonGlow_Stop
                                elseif conf.glow.mode == "autocast" then
                                    Glower.AutoCastGlow_Start( b, b.glowColor )
                                    b.glowStop = Glower.AutoCastGlow_Stop
                                elseif conf.glow.mode == "pixel" then
                                    Glower.PixelGlow_Start( b, b.glowColor )
                                    b.glowStop = Glower.PixelGlow_Stop
                                end

                                b.glowing = true
                            elseif not glowing and b.glowing then
                                b:glowStop()
                                b.glowing = false
                            end
                        else
                            if b.glowing then
                                b:glowStop()
                                b.glowing = false
                            end
                        end
                    end
                end

                local postGlow = debugprofilestop()

                if self.flashReady and conf.flash.enabled and LSF and ( InCombatLockdown() or not conf.flash.combat ) then
                    self.flashTimer = self.flashTimer - elapsed
                    self.flashWarnings = self.flashWarnings or {}
                    self.lastFlashFrames = self.lastFlashFrames or {}

                    local a = self.Buttons[ 1 ].Action
                    local changed = self.lastFlash ~= a

                    if a and ( fullUpdate or changed ) then
                        madeUpdate = true

                        if changed then
                            for frame in pairs( self.lastFlashFrames ) do
                                frame:Hide()
                                frame.flashDuration = 0
                                self.lastFlashFrames[ frame ] = nil
                            end
                        end

                        self.flashTimer = conf.flash.speed or 0.4

                        local ability = class.abilities[ a ]

                        self.flashColor = self.flashColor or {}
                        self.flashColor.r, self.flashColor.g, self.flashColor.b = unpack( conf.flash.color )

                        catchFlash = GetTime()
                        table.wipe( lastFramesFlashed )

                        if ability.item then
                            local iname = LSF.ItemName( ability.item )
                            if LSF.Flashable( iname ) then
                                LSF.FlashItem( iname, self.flashColor, conf.flash.size, conf.flash.brightness, conf.flash.blink, nil, profile.flashTexture, conf.flash.fixedSize, conf.flash.fixedBrightness )
                            elseif conf.flash.suppress and not self.flashWarnings[ iname ] then
                                self.flashWarnings[ iname ] = true
                                -- Hekili:Error( "|cffff0000WARNING|r - Could not flash recommended item '" .. iname .. "' (" .. self.id .. ")." )
                            end
                        else
                            local aFlash = ability.flash
                            if aFlash then
                                local flashable = false

                                if type( aFlash ) == "table" then
                                    local lastSpell
                                    for _, spell in ipairs( aFlash ) do
                                        lastSpell = spell
                                        if LSF.Flashable( spell ) then
                                            flashable = true
                                            break
                                        end
                                    end
                                    aFlash = lastSpell
                                else
                                    flashable = LSF.Flashable( aFlash )
                                end

                                if flashable then
                                    LSF.FlashAction( aFlash, self.flashColor, conf.flash.size, conf.flash.brightness, conf.flash.blink, nil, profile.flashTexture, conf.flash.fixedSize, conf.flash.fixedBrightness )
                                elseif conf.flash.suppress and not self.flashWarnings[ aFlash ] then
                                    self.flashWarnings[ aFlash ] = true
                                    -- Hekili:Error( "|cffff0000WARNING|r - Could not flash recommended action '" .. aFlash .. "' (" .. self.id .. ")." )
                                end
                            else
                                local id = ability.known

                                if id == nil or type( id ) ~= "number" then
                                    id = ability.id
                                end

                                local sname = LSF.SpellName( id )

                                if sname then
                                    if LSF.Flashable( sname ) then
                                        LSF.FlashAction( sname, self.flashColor, conf.flash.size, conf.flash.brightness, conf.flash.blink, nil, profile.flashTexture, conf.flash.fixedSize, conf.flash.fixedBrightness )
                                    elseif not self.flashWarnings[ sname ] then
                                        self.flashWarnings[ sname ] = true
                                        -- Hekili:Error( "|cffff0000WARNING|r - Could not flash recommended ability '" .. sname .. "' (" .. self.id .. ")." )
                                    end
                                end
                            end
                        end

                        catchFlash = nil
                        for frame, status in pairs( lastFramesFlashed ) do
                            if status ~= 0 then
                                self.lastFlashFrames[ frame ] = 1
                                if frame.texture ~= profile.flashTexture then
                                    frame.FlashTexture:SetTexture( profile.flashTexture )
                                    frame.texture = profile.flashTexture
                                end
                            end
                        end
                        self.lastFlash = a
                    end
                end

                local postFlash = debugprofilestop()

                if fullUpdate then
                    local b = self.Buttons[ 1 ]

                    if conf.targets.enabled then
                        madeUpdate = true

                        local tMin, tMax = 0, 0
                        local mode = profile.toggles.mode.value
                        local spec = state.spec.id and profile.specs[ state.spec.id ]

                        if self.id == 'Primary' then
                            if ( mode == 'dual' or mode == 'single' or mode == 'reactive' ) then tMax = 1
                            elseif mode == 'aoe' then tMin = spec and spec.aoe or 3 end
                        elseif self.id == 'AOE' then tMin = spec and spec.aoe or 3 end

                        local detected = ns.getNumberTargets()
                        local shown = detected

                        if tMin > 0 then
                            shown = max(tMin, shown)
                        end
                        if tMax > 0 then
                            shown = min(tMax, shown)
                        end

                        if tMax == 1 or shown > 1 then
                            local color = detected < shown and "|cFFFF0000" or ( shown < detected and "|cFF00C0FF" or "" )
                            b.Targets:SetText( color .. shown .. "|r")
                            b.targetShown = true
                        else
                            b.Targets:SetText(nil)
                            b.targetShown = false
                        end
                    elseif b.targetShown then
                        madeUpdate = true
                        b.Targets:SetText(nil)
                    end
                end

                local postTargets = debugprofilestop()

                self.delayTimer = self.delayTimer - elapsed

                if fullUpdate and self.Buttons[ 1 ].ExactTime then
                    madeUpdate = true

                    local b = self.Buttons[ 1 ]
                    local a = b.Ability

                    local delay = b.ExactTime - now
                    local earliest_time = 0

                    if delay > 0 then
                        local start, duration = 0, 0

                        if a.gcd ~= "off" then
                            start, duration = GetSpellCooldown( 61304 )
                            if start > 0 then earliest_time = start + duration - now end
                        end

                        start, duration = select( 4, UnitCastingInfo( "player" ) )
                        if start and start > 0 then earliest_time = max( ( start / 1000 ) + ( duration / 1000 ) - now, earliest_time ) end

                        local rStart, rDuration = 0, 0
                        if a.item then
                            rStart, rDuration = C_Item.GetItemCooldown( a.item )
                        else
                            if a.cooldown > 0 or a.spendType ~= "runes" then
                                rStart, rDuration = GetSpellCooldown( a.id )
                            end
                        end
                        if rStart > 0 then earliest_time = max( earliest_time, rStart + rDuration - now ) end
                    end

                    if conf.delays.type == "TEXT" then
                        if self.delayIconShown then
                            b.DelayIcon:Hide()
                            self.delayIconShown = false
                        end

                        if delay > earliest_time + 0.05 then
                            b.DelayText:SetText( format( "%.1f", delay ) )
                            self.delayTextShown = true
                        else
                            b.DelayText:SetText( nil )
                            self.delayTextShown = false
                        end

                    elseif conf.delays.type == "ICON" then
                        if self.delayTextShown then
                            b.DelayText:SetText(nil)
                            self.delayTextShown = false
                        end

                        if delay > earliest_time + 0.05 then
                            b.DelayIcon:Show()
                            b.DelayIcon:SetAlpha( self.alpha )

                            self.delayIconShown = true

                            if delay < 0.5 then
                                b.DelayIcon:SetVertexColor( 0.0, 1.0, 0.0, 1.0 )
                            elseif delay < 1.5 then
                                b.DelayIcon:SetVertexColor( 1.0, 1.0, 0.0, 1.0 )
                            else
                                b.DelayIcon:SetVertexColor( 1.0, 0.0, 0.0, 1.0 )
                            end
                        else
                            b.DelayIcon:Hide()
                            b.delayIconShown = false

                        end
                    else
                        if self.delayTextShown then
                            b.DelayText:SetText( nil )
                            self.delayTextShown = false
                        end
                        if self.delayIconShown then
                            b.DelayIcon:Hide()
                            self.delayIconShown = false
                        end
                    end

                    b.EarliestTime = earliest_time
                end

                self.rangeTimer = self.rangeTimer - elapsed
                if fullUpdate or self.rangeTimer < 0 then
                    madeUpdate = true

                    for i, b in ipairs( self.Buttons ) do
                        local a = b.Ability

                        if a and a.id then
                            local outOfRange = false
                            local desaturated = false

                            if conf.range.enabled and UnitCanAttack( "player", "target" ) then
                                if conf.range.type == "melee" then
                                    outOfRange = ( LRC:GetRange( "target" ) or 10 ) > 7
                                elseif conf.range.type == "ability" then
                                    local name = a.rangeSpell or a.itemSpellName or a.actualName or a.name
                                    if name then outOfRange = LSR.IsSpellInRange( name, "target" ) == 0 end
                                end
                            end

                            if outOfRange and not b.outOfRange then
                                b.Texture:SetVertexColor(1.0, 0.0, 0.0, 1.0)
                                b.outOfRange = true
                                desaturated = true
                            elseif b.outOfRange and not outOfRange then
                                b.Texture:SetVertexColor(1.0, 1.0, 1.0, 1.0)
                                b.outOfRange = false
                                desaturated = false
                            end

                            if not b.outOfRange then
                                local _, unusable

                                if a.itemCd or a.item then
                                    unusable = not IsUsableItem( a.itemCd or a.item )
                                else
                                    _, unusable = IsUsableSpell( a.actualName or a.name )
                                end

                                if i == 1 and ( conf.delays.fade or conf.delays.desaturate ) then
                                    local delay = b.ExactTime and ( b.ExactTime - now ) or 0
                                    local earliest_time = b.EarliestTime or delay
                                    if delay > earliest_time + 0.05 then
                                        if conf.delays.fade then unusable = true end
                                        if conf.delays.desaturate then desaturated = true end
                                    end
                                end

                                if unusable and not b.unusable then
                                    b.Texture:SetVertexColor(0.4, 0.4, 0.4, 1.0)
                                    b.unusable = true
                                elseif b.unusable and not unusable then
                                    b.Texture:SetVertexColor(1.0, 1.0, 1.0, 1.0)
                                    b.unusable = false
                                end
                            end

                            if desaturated and not b.desaturated then
                                b.Texture:SetDesaturated(true)
                                b.desaturated = true
                            elseif b.desaturated and not desaturated then
                                b.Texture:SetDesaturated(false)
                                b.desaturated = false
                            end
                        end
                    end

                    self.rangeTimer = pulseRange
                end

                local postRange = debugprofilestop()
                local finish = debugprofilestop()

                if madeUpdate then
                    if self.updateTime then
                        local newTime = self.updateTime * self.updateCount + ( finish - init )
                        self.updateCount = self.updateCount + 1
                        self.updateTime = newTime / self.updateCount

                        self.updateMax = max( self.updateMax, finish - init )
                        self.postRecs = max( self.postRecs, postRecs - init )
                        self.postGlow = max( self.postGlow, postGlow - postRecs )
                        self.postRange = max( self.postRange, postRange - postGlow )
                        self.postFlash = max( self.postFlash, postFlash - postRange )
                        self.postTargets = max( self.postTargets, postTargets - postFlash )
                        self.postDelay = max( self.postDelay, finish - postTargets )
                    else
                        self.updateCount = 1
                        self.updateTime = finish - init
                        self.updateMax = finish - init

                        self.postRecs = postRecs - init
                        self.postGlow = postGlow - postRecs
                        self.postRange = postRange - postGlow
                        self.postFlash = postFlash - postRange
                        self.postTargets = postTargets - postFlash
                        self.postDelay = finish - postTargets
                    end
                end
            end
        end

        Hekili:ProfileCPU( "HekiliDisplay" .. id .. ":OnUpdate", d.OnUpdate )

        function d:UpdateAlpha()
            if not self.Active then
                self:SetAlpha( 0 )
                self:Hide()
                self.alpha = 0
                return
            end

            local preAlpha = self.alpha or 0
            local newAlpha = CalculateAlpha( self.id )

            if preAlpha > 0 and newAlpha == 0 then
                -- self:Deactivate()
                self:SetAlpha( 0 )
                self.alphaCheck = 0.5
            else
                if preAlpha == 0 and newAlpha > 0 then
                    Hekili:ForceUpdate( "DISPLAY_ALPHA_CHANGED:" .. d.id .. ":" .. preAlpha .. ":" .. newAlpha .. ":" .. GetTime() )
                end
                self:SetAlpha( newAlpha )
                self:Show()
            end

            self.alpha = newAlpha
        end

        function d:RefreshCooldowns( event )
            local gStart = GetSpellCooldown( 61304 )
            local cStart = ( select( 4, UnitCastingInfo( "player" ) ) or select( 4, UnitChannelInfo( "player" ) ) or 0 ) / 1000

            local now = GetTime()
            local conf = Hekili.DB.profile.displays[ self.id ]

            for i, rec in ipairs( self.Recommendations ) do
                local button = self.Buttons[ i ]

                if button.Action and button.Action == rec.actionName then
                    local cd = button.Cooldown
                    local ability = button.Ability

                    local start, duration, enabled, modRate = 0, 0, 1, 1

                    if ability.item then
                        start, duration, enabled, modRate = GetItemCooldown( ability.item )
                    elseif not ability.empowered then
                        start, duration, enabled, modRate = GetSpellCooldown( ability.id )
                    end

                    if i == 1 and conf.delays.extend and rec.exact_time > max( now, start + duration ) then
                        start = ( start > 0 and start ) or ( cStart > 0 and cStart ) or ( gStart > 0 and gStart ) or max( state.gcd.lastStart, state.combat )
                        duration = rec.exact_time - start

                    elseif enabled and enabled == 0 then
                        start = 0
                        duration = 0
                        modRate = 1
                    end

                    if cd.lastStart ~= start or cd.lastDuration ~= duration then
                        cd:SetCooldown( start, duration, modRate )
                        cd.lastStart = start
                        cd.lastDuration = duration
                    end

                    if i == 1 and ability.empowered and conf.empowerment.glow then
                        if state.empowerment.start > 0 and duration == 0 then
                            button.Empowerment:Show()
                        else
                            button.Empowerment:Hide()
                        end
                    end
                end
            end
        end

        function d:OnEvent( event, ... )
            if not self.Recommendations then
                return
            end
            local conf = Hekili.DB.profile.displays[ self.id ]

            local init = debugprofilestop()

            if event == "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" then
                if conf.glow.enabled then
                    for i, b in ipairs( self.Buttons ) do
                        if i > 1 and not conf.glow.queued then
                            break
                        end

                        if not b.Action then
                            break
                        end

                        local a = b.Ability

                        if not b.glowing and a and a.id == ... then
                            b.glowColor = b.glowColor or {}

                            if conf.glow.coloring == "class" then
                                b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA()
                            elseif conf.glow.coloring == "custom" then
                                b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color)
                            else
                                b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1
                            end

                            if conf.glow.mode == "default" then
                                Glower.ButtonGlow_Start( b, b.glowColor )
                                b.glowStop = Glower.ButtonGlow_Stop
                            elseif conf.glow.mode == "autocast" then
                                Glower.AutoCastGlow_Start( b, b.glowColor )
                                b.glowStop = Glower.AutoCastGlow_Stop
                            elseif conf.glow.mode == "pixel" then
                                Glower.PixelGlow_Start( b, b.glowColor )
                                b.glowStop = Glower.PixelGlow_Stop
                            end

                            b.glowing = true
                        end
                    end
                end
            elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" then
                if conf.glow.enabled then
                    for i, b in ipairs(self.Buttons) do
                        if i > 1 and not conf.glow.queued then
                            break
                        end

                        if not b.Action then
                            break
                        end

                        local a = b.Ability

                        if b.glowing and ( not a or a.id == ... ) then
                            b:glowStop()
                            b.glowing = false
                        end
                    end
                end
            elseif kbEvents[ event ] then
                self:UpdateKeybindings()

            elseif alphaUpdateEvents[ event ] then
                if event == "CLIENT_SCENE_OPENED" then
                    if ... == 1 then -- Minigame.
                        Hekili.ClientScene = true
                    end
                elseif event == "CLIENT_SCENE_CLOSED" then
                    Hekili.ClientScene = nil
                end

                self:UpdateAlpha()

            end

            if flashEvents[ event ] then
                self.flashReady = false
                C_Timer.After( 3, function()
                    self.flashReady = true
                end )
            end

            if event == "CURRENT_SPELL_CAST_CHANGED" then
                local b = self.Buttons[ 1 ]

                if conf.glow.highlight then
                    local ability = b.Ability
                    local isItem, id = false, ability and ability.id

                    if id and id < 0 then
                        isItem = true
                        id = ability.item
                    end

                    if id and ( isItem and IsCurrentItem( id ) or IsCurrentSpell( id ) ) then --  and b.ExactTime > GetTime() then
                        b.Highlight:Show()
                    else
                        b.Highlight:Hide()
                    end
                elseif b.Highlight:IsShown() then
                    b.Highlight:Hide()
                end
            end

            local finish = debugprofilestop()

            if self.eventTime then
                local newTime = self.eventTime * self.eventCount + finish - init
                self.eventCount = self.eventCount + 1
                self.eventTime = newTime / self.eventCount

                if finish - init > self.eventMax then
                    self.eventMax = finish - init
                    self.eventMaxType = event
                end
            else
                self.eventCount = 1
                self.eventTime = finish - init
                self.eventMax = finish - init
                self.eventMaxType = event
            end
        end

        Hekili:ProfileCPU( "HekiliDisplay" .. id .. ":OnEvent", d.OnEvent )

        function d:Activate()
            if not self.Active then
                self.Active = true

                self.Recommendations = self.Recommendations or ( ns.queue and ns.queue[ self.id ] )
                self.NewRecommendations = true

                self.alphaCheck = 0
                self.auraTimer = 0
                self.delayTimer = 0
                self.flashTimer = 0
                self.glowTimer = 0
                self.rangeTimer = 0
                self.recTimer = 0
                self.refreshTimer = 0
                self.targetTimer = 0

                self.lastUpdate = 0

                self:SetScript( "OnUpdate", self.OnUpdate )
                self:SetScript( "OnEvent", self.OnEvent )

                if not self.Initialized then
                    -- Update Cooldown Wheels.
                    -- self:RegisterEvent( "ACTIONBAR_UPDATE_USABLE" )
                    -- self:RegisterEvent( "ACTIONBAR_UPDATE_COOLDOWN" )
                    -- self:RegisterEvent( "SPELL_UPDATE_COOLDOWN" )
                    -- self:RegisterEvent( "SPELL_UPDATE_USABLE" )

                    -- Show/Hide Overlay Glows.
                    self:RegisterEvent( "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" )
                    self:RegisterEvent( "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" )

                    -- Recalculate Alpha/Visibility.
                    for e in pairs( alphaUpdateEvents ) do
                        self:RegisterEvent( e )
                    end

                    -- Recheck spell displays if spells have changed.
                    self:RegisterEvent( "SPELLS_CHANGED" )
                    self:RegisterEvent( "CURRENT_SPELL_CAST_CHANGED" )

                    -- Update keybindings.
                    for k in pairs( kbEvents ) do
                        self:RegisterEvent( k )
                    end

                    for k in pairs( flashEvents ) do
                        self:RegisterEvent( k )
                    end

                    self.Initialized = true
                end

                -- Hekili:ProcessHooks( self.id )
            end
        end

        function d:Deactivate()
            self.Active = false

            self:SetScript( "OnUpdate", nil )
            self:SetScript( "OnEvent", nil )

            for i, b in ipairs( self.Buttons ) do
                b:Hide()
            end
        end


        function d:GetPerimeterButtons()
            local left, right, top, bottom
            local lPos, rPos, tPos, bPos

            for i = 1, self.numIcons do
                local button = self.Buttons[ i ]

                if i == 1 then
                    lPos = button:GetLeft()
                    rPos = button:GetRight()
                    tPos = button:GetTop()
                    bPos = button:GetBottom()

                    left = button
                    right = button
                    top = button
                    bottom = button
                else
                    if button:GetLeft() < lPos then
                        lPos = button:GetLeft()
                        left = button
                    end

                    if button:GetRight() > rPos then
                        rPos = button:GetRight()
                        right = button
                    end

                    if button:GetTop() > tPos then
                        tPos = button:GetTop()
                        top = button
                    end

                    if button:GetBottom() < bPos then
                        bPos = button:GetBottom()
                        bottom = button
                    end
                end
            end

            return left, right, top, bottom
        end

        -- function d:UpdatePerformance( now, used, newRecs )
            --[[
            if not InCombatLockdown() then
                self.combatUpdates.last = 0
                return
            elseif self.combatUpdates.last == 0 then
                self.combatUpdates.last = now - used
            end

            if used == nil then return end
            -- used = used / 1000 -- ms to sec.

            if self.combatTime.samples == 0 then
                self.combatTime.fastest = used
                self.combatTime.slowest = used
                self.combatTime.average = used

                self.combatTime.samples = 1
            else
                if used < self.combatTime.fastest then self.combatTime.fastest = used end
                if used > self.combatTime.slowest then
                    self.combatTime.slowest = used
                end

                self.combatTime.average = ( ( self.combatTime.average * self.combatTime.samples ) + used ) / ( self.combatTime.samples + 1 )
                self.combatTime.samples = self.combatTime.samples + 1
            end

            if self.combatUpdates.samples == 0 or self.combatUpdates.last == 0 then
                if self.combatUpdates.last == 0 then
                    self.combatUpdates.last = now
                else
                    local interval = now - self.combatUpdates.last
                    self.combatUpdates.last = now

                    self.combatUpdates.shortest = interval
                    self.combatUpdates.longest = interval
                    self.combatUpdates.average = interval

                    self.combatUpdates.samples = 1
                end
            else
                local interval = now - self.combatUpdates.last
                self.combatUpdates.last = now

                if interval < self.combatUpdates.shortest then
                    self.combatUpdates.shortest = interval
                    self.combatUpdates.shortEvents = nil

                    local e = 0
                    for k in pairs( self.eventsTriggered ) do
                        if e == 0 then self.combatUpdates.shortEvents = k; e = 1
                        else self.combatUpdates.shortEvents = self.combatUpdates.shortEvents .. "|" .. k end
                    end
                end

                if interval > self.combatUpdates.longest  then
                    self.combatUpdates.longest = interval
                    self.combatUpdates.longEvents = nil

                    local e = 0
                    for k in pairs( self.eventsTriggered ) do
                        if e == 0 then self.combatUpdates.longEvents = k; e = 1
                        else self.combatUpdates.longEvents = self.combatUpdates.longEvents .. "|" .. k end
                    end
                end

                self.combatUpdates.average = ( ( self.combatUpdates.average * self.combatUpdates.samples ) + interval ) / ( self.combatUpdates.samples + 1 )
                self.combatUpdates.samples = self.combatUpdates.samples + 1
            end

            if self.id == "Primary" then
                self.successEvents = self.successEvents or {}
                self.failEvents = self.failEvents or {}

                local events = newRecs and self.successEvents or self.failEvents

                for k in pairs( self.eventsTriggered ) do
                    if events[ k ] then events[ k ] = events[ k ] + 1
                    else events[ k ] = 1 end
                end

                table.wipe( self.eventsTriggered )
            end ]]
        -- end

        ns.queue[id] = ns.queue[id] or {}
        d.Recommendations = ns.queue[id]

        ns.UI.Buttons[id] = ns.UI.Buttons[id] or {}
        d.Buttons = ns.UI.Buttons[id]

        for i = 1, 10 do
            d.Buttons[ i ] = self:CreateButton( id, i )
            d.Buttons[ i ]:Hide()

            if self:IsDisplayActive( id ) and i <= conf.numIcons then
                if d.Recommendations[ i ] and d.Recommendations[ i ].actionName then
                    d.Buttons[ i ]:Show()
                end
            end

            if MasqueGroup then
                MasqueGroup:AddButton( d.Buttons[i], { Icon = d.Buttons[ i ].Texture, Cooldown = d.Buttons[ i ].Cooldown } )
            end
        end

        if d.forceElvUpdate then
            local E = _G.ElvUI and ElvUI[1]
            E:UpdateCooldownOverride( 'global' )
            d.forceElvUpdate = nil
        end

        if d.flashReady == nil then
            C_Timer.After( 3, function()
                d.flashReady = true
            end )
        end
    end


    function Hekili:CreateCustomDisplay( id )
        local conf = rawget( self.DB.profile.displays, id )
        if not conf then return end

        dPool[ id ] = dPool[ id ] or CreateFrame( "Frame", "HekiliDisplay" .. id, UIParent )
        local d = dPool[ id ]
        self:ProfileFrame( "HekiliDisplay" .. id, d )

        d.id = id

        local scale = self:GetScale()
        local border = 2

        d:SetSize( scale * ( border + conf.primaryWidth ), scale * (border + conf.primaryHeight ) )
        d:SetPoint( "CENTER", nil, "CENTER", conf.x, conf.y )
        d:SetFrameStrata( "MEDIUM" )
        d:SetClampedToScreen( true )
        d:EnableMouse( false )
        d:SetMovable( true )

        d.Activate = HekiliDisplayPrimary.Activate
        d.Deactivate = HekiliDisplayPrimary.Deactivate
        d.RefreshCooldowns = HekiliDisplayPrimary.RefreshCooldowns
        d.UpdateAlpha = HekiliDisplayPrimary.UpdateAlpha
        d.UpdateKeybindings = HekiliDisplayPrimary.UpdateKeybindings

        ns.queue[id] = ns.queue[id] or {}
        d.Recommendations = ns.queue[id]

        ns.UI.Buttons[id] = ns.UI.Buttons[id] or {}
        d.Buttons = ns.UI.Buttons[id]

        for i = 1, 10 do
            d.Buttons[i] = self:CreateButton(id, i)
            d.Buttons[i]:Hide()

            if self.DB.profile.enabled and self:IsDisplayActive(id) and i <= conf.numIcons then
                if d.Recommendations[i] and d.Recommendations[i].actionName then
                    d.Buttons[i]:Show()
                end
            end

            if MasqueGroup then
                MasqueGroup:AddButton(d.Buttons[i], {Icon = d.Buttons[i].Texture, Cooldown = d.Buttons[i].Cooldown})
            end
        end
    end

    local dispActive = {}
    local listActive = {}
    local actsActive = {}

    function Hekili:UpdateDisplayVisibility()
        local profile = self.DB.profile
        local displays = ns.UI.Displays

        for key in pairs( dispActive ) do
            dispActive[ key ] = nil
        end

        for list in pairs( listActive ) do
            listActive[ list ] = nil
        end

        for a in pairs( actsActive ) do
            actsActive[ a ] = nil
        end

        local specEnabled = GetSpecialization()
        specEnabled = specEnabled and GetSpecializationInfo( specEnabled )

        if class.specs[ specEnabled ] then
            specEnabled = specEnabled and rawget( profile.specs, specEnabled )
            specEnabled = specEnabled and rawget( specEnabled, "enabled" ) or false
        else
            specEnabled = false
        end

        if profile.enabled and specEnabled then
            for i, display in pairs( profile.displays ) do
                if display.enabled then
                    if i == 'AOE' then
                        dispActive[i] = ( profile.toggles.mode.value == 'dual' or profile.toggles.mode.value == "reactive" ) and 1 or nil
                    elseif i == 'Interrupts' then
                        dispActive[i] = ( profile.toggles.interrupts.value and profile.toggles.interrupts.separate ) and 1 or nil
                    elseif i == 'Defensives' then
                        dispActive[i] = ( profile.toggles.defensives.value and profile.toggles.defensives.separate ) and 1 or nil
                    elseif i == 'Cooldowns' then
                        dispActive[i] = ( profile.toggles.cooldowns.value and profile.toggles.cooldowns.separate ) and 1 or nil
                    else
                        dispActive[i] = 1
                    end

                    if dispActive[i] == nil and self.Config then
                        dispActive[i] = 2
                    end

                    if dispActive[i] and displays[i] then
                        if not displays[i].Active then displays[i]:Activate() end
                        displays[i].NewRecommendations = true
                    end
                else
                    if displays[i] and displays[i].Active then
                        displays[i]:Deactivate()
                    end
                end
            end

            for packName, pack in pairs( profile.packs ) do
                if pack.spec == 0 or pack.spec == state.spec.id then
                    for listName, list in pairs( pack.lists ) do
                        listActive[ packName .. ":" .. listName ] = true

                        -- NYI:  We can cache if abilities are disabled here as well to reduce checking in ProcessHooks.
                        for a, entry in ipairs( list ) do
                            if entry.enabled and entry.action then
                                actsActive[ packName .. ":" .. listName .. ":" .. a ] = true
                            end
                        end
                    end
                end
            end
        else
            for _, display in pairs( displays ) do
                if display.Active then
                    display:Deactivate()
                end
            end
        end

        for i, d in pairs( displays ) do
            d:UpdateAlpha()
        end
    end

    function Hekili:ReviewPacks()
        local profile = self.DB.profile

        for list in pairs( listActive ) do
            listActive[ list ] = nil
        end

        for a in pairs( actsActive ) do
            actsActive[ a ] = nil
        end

        for packName, pack in pairs( profile.packs ) do
            if pack.spec == 0 or pack.spec == state.spec.id then
                for listName, list in pairs( pack.lists ) do
                    listActive[ packName .. ":" .. listName ] = true

                    -- NYI:  We can cache if abilities are disabled here as well to reduce checking in ProcessHooks.
                    for a, entry in ipairs( list ) do
                        if entry.enabled and entry.action and class.abilities[ entry.action ] then
                            actsActive[ packName .. ":" .. listName .. ":" .. a ] = true
                        end
                    end
                end
            end
        end
    end

    function Hekili:IsDisplayActive( display, config )
        if config then
            return dispActive[ display ] == 1
        end
        return dispActive[display] ~= nil
    end

    function Hekili:IsListActive( pack, list )
        return pack == "UseItems" or ( listActive[ pack .. ":" .. list ] == true )
    end

    function Hekili:IsActionActive( pack, list, action )
        return pack == "UseItems" or ( actsActive[ pack .. ":" .. list .. ":" .. action ] == true )
    end

    function Hekili:DumpActionActive()
        DevTools_Dump( actsActive )
    end


    -- Separate the recommendations engine from each display.
    Hekili.Engine = CreateFrame( "Frame", "HekiliEngine" )

    Hekili.Engine.refreshTimer = 1
    Hekili.Engine.eventsTriggered = {}

    local framesUsed = 0
    local framesTimes = 0

    function Hekili.Engine:UpdatePerformance( wasted )
        -- Only track in combat.
        if not ( self.firstThreadCompleted and InCombatLockdown() ) then
            self.activeThreadTime = 0
            framesUsed = 0
            framesTimes = 0
            return
        end

        if self.firstThreadCompleted then
            local now = debugprofilestop()
            local timeSince = now - self.activeThreadStart

            self.lastUpdate = now

            if self.threadUpdates then
                local updates = self.threadUpdates.updates
                local total = updates + 1

                if framesUsed > 0 then
                    local frameCount = ( self.threadUpdates.framesWorked or 0 ) + framesUsed
                    self.threadUpdates.meanFrameTime = ( self.threadUpdates.meanFrameTime * self.threadUpdates.framesWorked + framesTimes ) / frameCount
                    self.threadUpdates.framesWorked  = frameCount
                end

                if wasted then
                    -- Capture thrown away computation time due to forced resets.
                    self.threadUpdates.meanWasted    = ( self.threadUpdates.meanWasted    * updates + self.activeThreadTime   ) / total
                    self.threadUpdates.totalWasted   = ( self.threadUpdates.totalWasted   + self.activeThreadTime             )

                    if self.activeThreadTime   > self.threadUpdates.peakWasted    then self.threadUpdates.peakWasted    = self.activeThreadTime end
                else
                    self.threadUpdates.meanClockTime = ( self.threadUpdates.meanClockTime * updates + timeSince               ) / total
                    self.threadUpdates.meanWorkTime  = ( self.threadUpdates.meanWorkTime  * updates + self.activeThreadTime   ) / total
                    self.threadUpdates.meanFrames    = ( self.threadUpdates.meanFrames    * updates + self.activeThreadFrames ) / total

                    if timeSince               > self.threadUpdates.peakClockTime then self.threadUpdates.peakClockTime = timeSince               end
                    if self.activeThreadTime   > self.threadUpdates.peakWorkTime  then self.threadUpdates.peakWorkTime  = self.activeThreadTime   end
                    if self.activeThreadFrames > self.threadUpdates.peakFrames    then self.threadUpdates.peakFrames    = self.activeThreadFrames end

                    self.threadUpdates.updates = total
                    self.threadUpdates.updatesPerSec = 1000 * total / ( now - self.threadUpdates.firstUpdate )
                end

            else
                self.threadUpdates = {
                    meanClockTime  = timeSince,
                    meanWorkTime   = self.activeThreadTime,
                    meanFrames     = self.activeThreadFrames or 1,
                    meanFrameTime  = framesTimes > 0 and framesTimes or ( 1000 / GetFramerate() ),
                    meanWasted     = 0,

                    firstUpdate    = now,
                    updates        = 1,
                    framesWorked   = framesUsed > 0 and framesUsed or 1,
                    updatesPerSec  = 1000 / ( self.activeThreadTime > 0 and self.activeThreadTime or 1 ),

                    peakClockTime  = timeSince,
                    peakWorkTime   = self.activeThreadTime,
                    peakFrames     = self.activeThreadFrames or 1,
                    peakWasted     = 0,

                    totalWasted    = 0
                }
            end
        end

        self.activeThreadTime = 0
    end


    Hekili.Engine:SetScript( "OnUpdate", function( self, elapsed )
        if not self.activeThread then
            self.refreshTimer = self.refreshTimer + elapsed
        end

        if Hekili.DB.profile.enabled and not Hekili.Pause then
            -- Set refresh rates to default values
            self.refreshRate = self.refreshRate or 0.25
            self.combatRate = self.combatRate or 0.2

            local thread = self.activeThread

            -- If there's no thread, then see if we have a reason to update.
            if ( not thread or coroutine.status( thread ) == "dead" ) and self.refreshTimer > ( self.criticalUpdate and self.combatRate or self.refreshRate ) then
                --[[ if thread and coroutine.status( thread ) == "suspended" then
                    -- We're going to break the thread and start over from the current display in progress.
                    self:UpdatePerformance( true )
                end ]]

                self.criticalUpdate = false
                self.superUpdate = false
                self.refreshTimer = 0

                self.activeThread = coroutine.create( Hekili.Update )

                self.activeThreadTime = 0
                self.activeThreadStart = debugprofilestop()
                self.activeThreadFrames = 0

                -- Use new frame budget calculation
                Hekili.maxFrameTime = calculateFrameBudget()

                thread = self.activeThread
            end

            -- If there's a thread, process for up to user preferred limits.
            if thread and coroutine.status( thread ) == "suspended" then
                framesUsed  = framesUsed  + 1
                framesTimes = framesTimes + elapsed * 1000

                self.activeThreadFrames = self.activeThreadFrames + 1
                Hekili.activeFrameStart = debugprofilestop()

                -- if HekiliEngine.threadUpdates then print( 1000 * elapsed, Hekili.maxFrameTime, HekiliEngine.threadUpdates.meanWorkTime, HekiliEngine.threadUpdates.meanFrames ) end
                local ok, err = coroutine.resume( thread )

                if not ok then
                    err = err .. "\n\n" .. debugstack( thread )
                    Hekili:Error( "Update: " .. err )

                    if Hekili.ActiveDebug then
                        Hekili:Debug( format( "Recommendation thread terminated due to error: %s", err and err:gsub( "%%", "%%%%" ) or "Unknown" ) )
                        Hekili:SaveDebugSnapshot( self.id )
                        Hekili.ActiveDebug = nil
                    end

                    pcall( error, err )
                end

                self.activeThreadTime = self.activeThreadTime + debugprofilestop() - Hekili.activeFrameStart

                if coroutine.status( thread ) == "dead" or err then
                    self.activeThread = nil

                    self.refreshRate = 0.5
                    self.combatRate = 0.2

                    if ok then
                        if self.firstThreadCompleted and not self.DontProfile then self:UpdatePerformance() end
                        self.firstThreadCompleted = true
                    end
                end

                if ok and err == "AutoSnapshot" then
                    self.DontProfile = true
                    Hekili:MakeSnapshot( true )
                    self.DontProfile = false
                end
            end
        end
    end )
    Hekili:ProfileFrame( "HekiliEngine", Hekili.Engine )


    function HekiliEngine:IsThreadActive()
        return self.activeThread and coroutine.status( self.activeThread ) == "suspended"
    end


    function Hekili:ForceUpdate( event, super )
        self.Engine.criticalUpdate = true
        if super then self.Engine.refreshTimer = self.Engine.refreshTimer + 0.1 end

        if self.Engine.firstForce == 0 then
            self.Engine.firstForce = GetTime()
        end

        if event then
            self.Engine.eventsTriggered[ event ] = true
        end
    end


    local LSM = LibStub("LibSharedMedia-3.0", true)

    function Hekili:CreateButton( dispID, id )
        local d = dPool[ dispID ]
        if not d then
            return
        end

        local conf = rawget( self.DB.profile.displays, dispID )
        if not conf then return end

        ns.queue[ dispID ][ id ] = ns.queue[ dispID ][ id ] or {}

        local bName = "Hekili_" .. dispID .. "_B" .. id
        local b = d.Buttons[ id ] or CreateFrame( "Button", bName, d )

        Hekili:ProfileFrame( bName, b )

        b.display = dispID
        b.index = id

        local scale = self:GetScale()

        local borderOffset = 0

        if conf.border.enabled and conf.border.fit then
            borderOffset = 2
        end

        if id == 1 then
            b:SetHeight( scale * ( ( conf.primaryHeight or 50 ) - borderOffset ) )
            b:SetWidth( scale * ( ( conf.primaryWidth or 50 ) - borderOffset  ) )
        else
            b:SetHeight( scale * ( ( conf.queue.height or 30 ) - borderOffset  ) )
            b:SetWidth( scale * ( ( conf.queue.width or 50 ) - borderOffset  ) )
        end

        -- Texture
        if not b.Texture then
            b.Texture = b:CreateTexture( nil, "ARTWORK" )
            b.Texture:SetTexture( "Interface\\ICONS\\Spell_Nature_BloodLust" )
            b.Texture:SetAllPoints( b )
        end

        b.texCoords = b.texCoords or {}
        local zoom = 1 - ( ( conf.zoom or 0) / 200 )

        if conf.keepAspectRatio then
            local biggest = id == 1 and max( conf.primaryHeight, conf.primaryWidth ) or max( conf.queue.height, conf.queue.width )
            local height = 0.5 * zoom * ( id == 1 and conf.primaryHeight or conf.queue.height ) / biggest
            local width = 0.5 * zoom * ( id == 1 and conf.primaryWidth or conf.queue.width ) / biggest

            b.texCoords[1] = 0.5 - width
            b.texCoords[2] = 0.5 + width
            b.texCoords[3] = 0.5 - height
            b.texCoords[4] = 0.5 + height

            b.Texture:SetTexCoord( unpack( b.texCoords ) )
        else
            local zoom = zoom / 2

            b.texCoords[1] = 0.5 - zoom
            b.texCoords[2] = 0.5 + zoom
            b.texCoords[3] = 0.5 - zoom
            b.texCoords[4] = 0.5 + zoom

            b.Texture:SetTexCoord( unpack( b.texCoords ) )
        end


        -- Initialize glow/noop if button has not yet been glowed.
        b.glowing = b.glowing or false
        b.glowStop = b.glowStop or function () end


        -- Indicator Icons.
        b.Icon = b.Icon or b:CreateTexture( nil, "OVERLAY" )
        b.Icon:SetSize( conf.indicators.width or 20, conf.indicators.height or 20 )

        local zoom = 1 - ( ( conf.indicators.zoom or 0) / 200 )

        if conf.indicators.keepAspectRatio and conf.indicators.height ~= conf.indicators.width then
            local biggest = max( conf.indicators.height or 20, conf.indicators.width or 20 )
            local height = 0.5 * zoom * ( conf.indicators.height or 20 ) / biggest
            local width = 0.5 * zoom * ( conf.indicators.width or 20 ) / biggest

            b.Icon:SetTexCoord( 0.5 - width, 0.5 + width, 0.5 - height, 0.5 + height )
        else
            b.Icon:SetTexCoord( 0, 1, 0, 1 )
        end

        local iconAnchor = conf.indicators.anchor or "RIGHT"

        b.Icon:ClearAllPoints()
        b.Icon:SetPoint( iconAnchor, b, iconAnchor, conf.indicators.x or 0, conf.indicators.y or 0 )
        b.Icon:Hide()


        -- Caption Text.
        b.Caption = b.Caption or b:CreateFontString( bName .. "_Caption", "OVERLAY" )

        local captionFont = conf.captions.font or conf.font
        b.Caption:SetFont( LSM:Fetch("font", captionFont), conf.captions.fontSize or 12, conf.captions.fontStyle or "OUTLINE" )

        local capAnchor = conf.captions.anchor or "BOTTOM"
        b.Caption:ClearAllPoints()
        b.Caption:SetPoint( capAnchor, b, capAnchor, conf.captions.x or 0, conf.captions.y or 0 )
        b.Caption:SetHeight( b:GetHeight() / 2 )
        b.Caption:SetJustifyV( capAnchor:match("RIGHT") and "RIGHT" or ( capAnchor:match( "LEFT" ) and "LEFT" or "MIDDLE" ) )
        b.Caption:SetJustifyH( conf.captions.align or "CENTER" )
        b.Caption:SetTextColor( unpack( conf.captions.color ) )
        b.Caption:SetWordWrap( false )

        local capText = b.Caption:GetText()
        b.Caption:SetText( nil )
        b.Caption:SetText( capText )


        -- Keybinding Text
        b.Keybinding = b.Keybinding or b:CreateFontString(bName .. "_KB", "OVERLAY")

        local queued = id > 1 and conf.keybindings.separateQueueStyle
        local kbFont = queued and conf.keybindings.queuedFont or conf.keybindings.font or conf.font

        b.Keybinding:SetFont( LSM:Fetch("font", kbFont), queued and conf.keybindings.queuedFontSize or conf.keybindings.fontSize or 12, queued and conf.keybindings.queuedFontStyle or conf.keybindings.fontStyle or "OUTLINE" )

        local kbAnchor = conf.keybindings.anchor or "TOPRIGHT"
        b.Keybinding:ClearAllPoints()
        b.Keybinding:SetPoint( kbAnchor, b, kbAnchor, conf.keybindings.x or 0, conf.keybindings.y or 0 )
        b.Keybinding:SetHeight( b:GetHeight() / 2 )
        b.Keybinding:SetJustifyH( kbAnchor:match("RIGHT") and "RIGHT" or ( kbAnchor:match( "LEFT" ) and "LEFT" or "CENTER" ) )
        b.Keybinding:SetJustifyV( kbAnchor:match("TOP") and "TOP" or ( kbAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE" ) )
        b.Keybinding:SetTextColor( unpack( queued and conf.keybindings.queuedColor or conf.keybindings.color ) )
        b.Keybinding:SetWordWrap( false )

        local kbText = b.Keybinding:GetText()
        b.Keybinding:SetText( nil )
        b.Keybinding:SetText( kbText )

        -- Cooldown Wheel
        if not b.Cooldown then
            b.Cooldown = CreateFrame( "Cooldown", bName .. "_Cooldown", b, "CooldownFrameTemplate" )
            if id == 1 then b.Cooldown:HookScript( "OnCooldownDone", function( self )
                    if b.Ability and b.Ability.empowered and conf.empowerment.glow and state.empowerment.spell == b.Ability.key then
                        b.Empowerment:Show()
                    else
                        b.Empowerment:Hide()
                    end
                end )
            end
        end
        b.Cooldown:ClearAllPoints()
        b.Cooldown:SetAllPoints( b )
        b.Cooldown:SetFrameStrata( b:GetFrameStrata() )
        b.Cooldown:SetFrameLevel( b:GetFrameLevel() + 1 )
        b.Cooldown:SetDrawBling( false )
        b.Cooldown:SetDrawEdge( false )

        b.Cooldown.noCooldownCount = conf.hideOmniCC

        if _G["ElvUI"] and not b.isRegisteredCooldown and ( ( id == 1 and conf.elvuiCooldown ) or ( id > 1 and conf.queue.elvuiCooldown ) ) then
            local E = unpack( ElvUI )

            local cd = b.Cooldown.CooldownSettings or {}
            cd.font = E.Libs.LSM:Fetch( "font", E.db.cooldown.fonts.font )
            cd.fontSize = E.db.cooldown.fonts.fontSize
            cd.fontOutline = E.db.cooldown.fonts.fontOutline
            b.Cooldown.CooldownSettings = cd

            E:RegisterCooldown( b.Cooldown )
            d.forceElvUpdate = true
        end

        -- Backdrop (for borders)
        b.Backdrop = b.Backdrop or Mixin( CreateFrame("Frame", bName .. "_Backdrop", b ), BackdropTemplateMixin )
        b.Backdrop:ClearAllPoints()
        b.Backdrop:SetWidth( b:GetWidth() + ( conf.border.thickness and ( 2 * conf.border.thickness ) or 2 ) )
        b.Backdrop:SetHeight( b:GetHeight() + ( conf.border.thickness and ( 2 * conf.border.thickness ) or 2 ) )

        local framelevel = b:GetFrameLevel()
        if framelevel > 0 then
            -- b.Backdrop:SetFrameStrata( "MEDIUM" )
            b.Backdrop:SetFrameLevel( framelevel - 1 )
        else
            local lowerStrata = frameStratas[ b:GetFrameStrata() ]
            lowerStrata = frameStratas[ lowerStrata - 1 ]
            b.Backdrop:SetFrameStrata( lowerStrata or "LOW" )
        end

        b.Backdrop:SetPoint( "CENTER", b, "CENTER" )
        b.Backdrop:Hide()

        if conf.border.enabled then
            b.Backdrop:SetBackdrop( {
                bgFile = nil,
                edgeFile = "Interface\\Buttons\\WHITE8X8",
                tile = false,
                tileSize = 0,
                edgeSize = conf.border.thickness or 1,
                insets = { left = -1, right = -1, top = -1, bottom = -1 }
            } )
            if conf.border.coloring == 'custom' then
                b.Backdrop:SetBackdropBorderColor( unpack( conf.border.color ) )
            else
                b.Backdrop:SetBackdropBorderColor( RAID_CLASS_COLORS[ class.file ]:GetRGBA() )
            end
            b.Backdrop:Show()
        else
            b.Backdrop:SetBackdrop( nil )
            b.Backdrop:SetBackdropColor( 0, 0, 0, 0 )
            b.Backdrop:Hide()
        end


        -- Primary Icon Stuff
        if id == 1 then
            -- Anchoring stuff for the queue.
            b:ClearAllPoints()
            b:SetPoint( "CENTER", d, "CENTER" )

            -- Highlight
            if not b.Highlight then
                b.Highlight = b:CreateTexture( nil, "OVERLAY" )
                b.Highlight:SetTexture( "Interface\\Buttons\\ButtonHilight-Square" )
                b.Highlight:SetAllPoints( b )
                b.Highlight:SetBlendMode( "ADD" )
                b.Highlight:Hide()
            end

            -- Target Counter
            b.Targets = b.Targets or b:CreateFontString( bName .. "_Targets", "OVERLAY" )

            local tarFont = conf.targets.font or conf.font
            b.Targets:SetFont( LSM:Fetch( "font", tarFont ), conf.targets.fontSize or 12, conf.targets.fontStyle or "OUTLINE" )

            local tarAnchor = conf.targets.anchor or "BOTTOM"
            b.Targets:ClearAllPoints()
            b.Targets:SetPoint( tarAnchor, b, tarAnchor, conf.targets.x or 0, conf.targets.y or 0 )
            b.Targets:SetHeight( b:GetHeight() / 2 )
            b.Targets:SetJustifyH( tarAnchor:match("RIGHT") and "RIGHT" or ( tarAnchor:match( "LEFT" ) and "LEFT" or "CENTER" ) )
            b.Targets:SetJustifyV( tarAnchor:match("TOP") and "TOP" or ( tarAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE" ) )
            b.Targets:SetTextColor( unpack( conf.targets.color ) )
            b.Targets:SetWordWrap( false )

            local tText = b.Targets:GetText()
            b.Targets:SetText( nil )
            b.Targets:SetText( tText )

            -- Aura Counter
            -- Disabled for Now
            --[[ b.Auras = b.Auras or b:CreateFontString(bName .. "_Auras", "OVERLAY")

            local auraFont = conf.auraFont or (ElvUI and "PT Sans Narrow" or "Arial Narrow")
            b.Auras:SetFont(LSM:Fetch("font", auraFont), conf.auraFontSize or 12, conf.auraFontStyle or "OUTLINE")
            b.Auras:SetSize(b:GetWidth(), b:GetHeight() / 2)

            local auraAnchor = conf.auraAnchor or "BOTTOM"
            b.Auras:ClearAllPoints()
            b.Auras:SetPoint(auraAnchor, b, auraAnchor, conf.xOffsetAuras or 0, conf.yOffsetAuras or 0)

            b.Auras:SetJustifyH(
                auraAnchor:match("RIGHT") and "RIGHT" or (auraAnchor:match("LEFT") and "LEFT" or "CENTER")
            )
            b.Auras:SetJustifyV(
                auraAnchor:match("TOP") and "TOP" or (auraAnchor:match("BOTTOM") and "BOTTOM" or "MIDDLE")
            )
            b.Auras:SetTextColor(1, 1, 1, 1) ]]


            -- Delay Counter
            b.DelayText = b.DelayText or b:CreateFontString( bName .. "_DelayText", "OVERLAY" )

            local delayFont = conf.delays.font or conf.font
            b.DelayText:SetFont( LSM:Fetch("font", delayFont), conf.delays.fontSize or 12, conf.delays.fontStyle or "OUTLINE" )

            local delayAnchor = conf.delays.anchor or "TOPLEFT"
            b.DelayText:ClearAllPoints()
            b.DelayText:SetPoint( delayAnchor, b, delayAnchor, conf.delays.x, conf.delays.y or 0 )
            b.DelayText:SetHeight( b:GetHeight() / 2 )

            b.DelayText:SetJustifyH( delayAnchor:match( "RIGHT" ) and "RIGHT" or ( delayAnchor:match( "LEFT" ) and "LEFT" or "CENTER") )
            b.DelayText:SetJustifyV( delayAnchor:match( "TOP" ) and "TOP" or ( delayAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE") )
            b.DelayText:SetTextColor( unpack( conf.delays.color ) )

            local dText = b.DelayText:GetText()
            b.DelayText:SetText( nil )
            b.DelayText:SetText( dText )


            -- Delay Icon
            b.DelayIcon = b.DelayIcon or b:CreateTexture( bName .. "_DelayIcon", "OVERLAY" )
            b.DelayIcon:SetSize( min( 20, max( 10, b:GetSize() / 3 ) ), min( 20, max( 10, b:GetSize() / 3 ) ) )
            b.DelayIcon:SetTexture( "Interface\\FriendsFrame\\StatusIcon-Online" )
            b.DelayIcon:SetDesaturated( true )
            b.DelayIcon:SetVertexColor( 1, 0, 0, 1 )

            b.DelayIcon:ClearAllPoints()
            b.DelayIcon:SetPoint( delayAnchor, b, delayAnchor, conf.delays.x or 0, conf.delays.y or 0 )
            b.DelayIcon:Hide()

            -- Empowerment
            b.Empowerment = b.Empowerment or b:CreateTexture( bName .. "_Empower", "OVERLAY" )
            b.Empowerment:SetAtlas( "bags-glow-artifact" )
            b.Empowerment:SetVertexColor( 1, 1, 1, 1 )

            b.Empowerment:ClearAllPoints()
            b.Empowerment:SetPoint( "TOPLEFT", b, "TOPLEFT", -1, 1 )
            b.Empowerment:SetPoint( "BOTTOMRIGHT", b, "BOTTOMRIGHT", 1, -1 )
            b.Empowerment:Hide()

            -- Overlay (for Pause)
            b.Overlay = b.Overlay or b:CreateTexture( nil, "OVERLAY" )
            b.Overlay:SetAllPoints( b )
            b.Overlay:SetAtlas( "creditsscreen-assets-buttons-pause" )
            b.Overlay:SetVertexColor( 1, 1, 1, 1 )
            -- b.Overlay:SetTexCoord( unpack( b.texCoords ) )
            b.Overlay:Hide()

        elseif id == 2 then
            -- Anchoring for the remainder.
            local queueAnchor = conf.queue.anchor or "RIGHT"
            local qOffsetX = ( conf.queue.offsetX or 5 )
            local qOffsetY = ( conf.queue.offsetY or 0 )

            b:ClearAllPoints()

            if queueAnchor:sub( 1, 5 ) == "RIGHT" then
                local dir, align = "RIGHT", queueAnchor:sub(6)
                b:SetPoint( align .. getInverseDirection(dir), "Hekili_" .. dispID .. "_B1", align .. dir, ( borderOffset + qOffsetX ) * scale, qOffsetY * scale )
            elseif queueAnchor:sub( 1, 4 ) == "LEFT" then
                local dir, align = "LEFT", queueAnchor:sub(5)
                b:SetPoint( align .. getInverseDirection(dir), "Hekili_" .. dispID .. "_B1", align .. dir, -1 * ( borderOffset + qOffsetX ) * scale, qOffsetY * scale )
            elseif queueAnchor:sub( 1, 3)  == "TOP" then
                local dir, align = "TOP", queueAnchor:sub(4)
                b:SetPoint( getInverseDirection(dir) .. align, "Hekili_" .. dispID .. "_B1", dir .. align, 0, ( borderOffset + qOffsetY ) * scale )
            else -- BOTTOM
                local dir, align = "BOTTOM", queueAnchor:sub(7)
                b:SetPoint( getInverseDirection(dir) .. align, "Hekili_" .. dispID .. "_B1", dir .. align, 0, -1 * ( borderOffset + qOffsetY ) * scale )
            end
        else
            local queueDirection = conf.queue.direction or "RIGHT"
            local btnSpacing = borderOffset + ( conf.queue.spacing or 5 )

            b:ClearAllPoints()

            if queueDirection == "RIGHT" then
                b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, btnSpacing * scale, 0 )
            elseif queueDirection == "LEFT" then
                b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, -1 * btnSpacing * scale, 0 )
            elseif queueDirection == "TOP" then
                b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, 0, btnSpacing * scale )
            else -- BOTTOM
                b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, 0, -1 * btnSpacing * scale )
            end
        end


        -- Caption Text.
        b.EmpowerLevel = b.EmpowerLevel or b:CreateFontString( bName .. "_EmpowerLevel", "OVERLAY" )

        local empowerFont = conf.empowerment.font or conf.font
        b.EmpowerLevel:SetFont( LSM:Fetch("font", empowerFont), conf.empowerment.fontSize or 12, conf.empowerment.fontStyle or "OUTLINE" )

        local empAnchor = conf.empowerment.anchor or "CENTER"
        b.EmpowerLevel:ClearAllPoints()
        b.EmpowerLevel:SetPoint( empAnchor, b, empAnchor, conf.empowerment.x or 0, conf.empowerment.y or 0 )
        -- b.EmpowerLevel:SetHeight( b:GetHeight() * 0.6 )
        b.EmpowerLevel:SetJustifyV( empAnchor:match("RIGHT") and "RIGHT" or ( empAnchor:match( "LEFT" ) and "LEFT" or "MIDDLE" ) )
        b.EmpowerLevel:SetJustifyH( conf.empowerment.align or "CENTER" )
        b.EmpowerLevel:SetTextColor( unpack( conf.empowerment.color ) )
        b.EmpowerLevel:SetWordWrap( false )

        local empText = b.EmpowerLevel:GetText()
        b.EmpowerLevel:SetText( nil )
        b.EmpowerLevel:SetText( empText )

        if conf.empowerment.enabled then b.EmpowerLevel:Show()
        else b.EmpowerLevel:Hide() end

        -- Mover Stuff.
        b:SetScript( "OnMouseDown", Button_OnMouseDown )
        b:SetScript( "OnMouseUp", Button_OnMouseUp )

        b:SetScript( "OnEnter", function( self )
            local H = Hekili

            --[[ if H.Config then
                Tooltip:SetOwner( self, "ANCHOR_TOPRIGHT" )
                Tooltip:SetBackdropColor( 0, 0, 0, 0.8 )

                Tooltip:SetText( "Hekili: " .. dispID  )
                Tooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 )
                Tooltip:Show()
                self:SetMovable( true )

            else ]]
            if ( H.Pause and d.HasRecommendations and b.Recommendation ) then
                H:ShowDiagnosticTooltip( b.Recommendation )
            end
        end )

        b:SetScript( "OnLeave", function(self)
            HekiliTooltip:Hide()
        end )

        Hekili:ProfileFrame( bName, b )

        b:EnableMouse( false )
        b:SetMovable( false )

        return b
    end
end

-- Builds and maintains the visible UI elements.
-- Buttons (as frames) are never deleted, but should get reused effectively.

local builtIns = {
    "Primary", "AOE", "Cooldowns", "Interrupts", "Defensives"
}

function Hekili:BuildUI()
    if not Masque then
        Masque = LibStub( "Masque", true )

        if Masque then
            Masque:Register( addon, MasqueUpdate, self )
            MasqueGroup = Masque:Group( addon )
        end
    end

    local LSM = LibStub( "LibSharedMedia-3.0" )

    ns.UI.Keyhandler = ns.UI.Keyhandler or CreateFrame( "Button", "Hekili_Keyhandler", UIParent )
    ns.UI.Keyhandler:RegisterForClicks( "AnyDown" )
    ns.UI.Keyhandler:SetScript( "OnClick", function( self, button, down )
        Hekili:FireToggle( button )
    end )
    Hekili:ProfileFrame( "KeyhandlerFrame", ns.UI.Keyhandler )

    local scaleFactor = self:GetScale()
    local mouseInteract = self.Pause

    -- Notification Panel
    local notif = self.DB.profile.notifications

    local f = ns.UI.Notification or CreateFrame( "Frame", "HekiliNotification", UIParent )
    Hekili:ProfileFrame( "HekiliNotification", f )

    f:SetSize( notif.width * scaleFactor, notif.height * scaleFactor )
    f:SetClampedToScreen( true )
    f:ClearAllPoints()
    f:SetPoint("CENTER", nil, "CENTER", notif.x, notif.y )

    f.Text = f.Text or f:CreateFontString( "HekiliNotificationText", "OVERLAY" )
    f.Text:SetAllPoints( f )
    f.Text:SetFont( LSM:Fetch( "font", notif.font ), notif.fontSize * scaleFactor, notif.fontStyle )
    f.Text:SetJustifyV("MIDDLE")
    f.Text:SetJustifyH("CENTER")
    f.Text:SetTextColor(1, 1, 1, 1)

    if not notif.enabled then f:Hide()
    else f.Text:SetText(nil); f:Show() end

    ns.UI.Notification = f
    -- End Notification Panel

    -- Displays
    for disp in pairs( self.DB.profile.displays ) do
        self:CreateDisplay( disp )
    end

    --if Hekili.Config then ns.StartConfiguration() end
    if MasqueGroup then
        MasqueGroup:ReSkin()
    end

    -- Check for a display that has been removed.
    for display, buttons in ipairs(ns.UI.Buttons) do
        if not Hekili.DB.profile.displays[display] then
            for i, _ in ipairs(buttons) do
                buttons[i]:Hide()
            end
        end
    end

    if Hekili.Config then
        ns.StartConfiguration(true)
    end
end

local T = ns.lib.Format.Tokens
local SyntaxColors = {}

function ns.primeTooltipColors()
    T = ns.lib.Format.Tokens
    --- Assigns a color to multiple tokens at once.
    local function Color(Code, ...)
        for Index = 1, select("#", ...) do
            SyntaxColors[select(Index, ...)] = Code
        end
    end
    Color( "|cffB266FF", T.KEYWORD ) -- Reserved Words

    Color( "|cffffffff", T.LEFTCURLY, T.RIGHTCURLY, T.LEFTBRACKET, T.RIGHTBRACKET, T.LEFTPAREN, T.RIGHTPAREN )

    Color( "|cffFF66FF", T.UNKNOWN,
        T.ADD,
        T.SUBTRACT,
        T.MULTIPLY,
        T.DIVIDE,
        T.POWER,
        T.MODULUS,
        T.CONCAT,
        T.VARARG,
        T.ASSIGNMENT,
        T.PERIOD,
        T.COMMA,
        T.SEMICOLON,
        T.COLON,
        T.SIZE,
        T.EQUALITY,
        T.NOTEQUAL,
        T.LT,
        T.LTE,
        T.GT,
        T.GTE )

    Color( "|cFFB2FF66", multiUnpack(ns.keys, ns.attr) )

    Color( "|cffFFFF00", T.NUMBER )
    Color( "|cff888888", T.STRING, T.STRING_LONG )
    Color( "|cff55cc55", T.COMMENT_SHORT, T.COMMENT_LONG )
    Color( "|cff55ddcc", -- Minimal standard Lua functions
        "assert",
        "error",
        "ipairs",
        "next",
        "pairs",
        "pcall",
        "print",
        "select",
        "tonumber",
        "tostring",
        "type",
        "unpack",
        -- Libraries
        "bit",
        "coroutine",
        "math",
        "string",
        "table"
    )
    Color( "|cffddaaff", -- Some of WoW's aliases for standard Lua functions
        -- math
        "abs",
        "ceil",
        "floor",
        "max",
        "min",
        -- string
        "format",
        "gsub",
        "strbyte",
        "strchar",
        "strconcat",
        "strfind",
        "strjoin",
        "strlower",
        "strmatch",
        "strrep",
        "strrev",
        "strsplit",
        "strsub",
        "strtrim",
        "strupper",
        "tostringall",
        -- table
        "sort",
        "tinsert",
        "tremove",
        "wipe" )
end


local SpaceLeft = {"(%()"}
local SpaceRight = {"(%))"}
local DoubleSpace = {"(!=)", "(~=)", "(>=*)", "(<=*)", "(&)", "(||)", "(+)", "(*)", "(-)", "(/)"}


local function Format(Code)
    for Index = 1, #SpaceLeft do
        Code = Code:gsub("%s-" .. SpaceLeft[Index] .. "%s-", " %1")
    end

    for Index = 1, #SpaceRight do
        Code = Code:gsub("%s-" .. SpaceRight[Index] .. "%s-", "%1 ")
    end

    for Index = 1, #DoubleSpace do
        Code = Code:gsub("%s-" .. DoubleSpace[Index] .. "%s-", " %1 ")
    end

    Code = Code:gsub("([^<>~!])(=+)", "%1 %2 ")
    Code = Code:gsub("%s+", " "):trim()
    return Code
end


local key_cache = setmetatable( {}, {
    __index = function( t, k )
        t[k] = k:gsub( "(%S+)%[(%d+)]", "%1.%2" )
        return t[k]
    end
})


function Hekili:ShowDiagnosticTooltip( q )
    if not q.actionName or not class.abilities[ q.actionName ].name then return end

    local tt = HekiliTooltip
    local fmt = ns.lib.Format

    tt:SetOwner( UIParent, "ANCHOR_CURSOR" )
    tt:SetText( class.abilities[ q.actionName ].name )
    tt:AddDoubleLine( q.listName .. " #" .. q.action, "+" .. ns.formatValue(round(q.time or 0, 2)), 1, 1, 1, 1, 1, 1 )

    if q.resources and q.resources[q.resource_type] then
        tt:AddDoubleLine(q.resource_type, ns.formatValue(q.resources[q.resource_type]), 1, 1, 1, 1, 1, 1)
    end

    if q.HookHeader or (q.HookScript and q.HookScript ~= "") then
        if q.HookHeader then
            tt:AddLine(" ")
            tt:AddLine(q.HookHeader)
        else
            tt:AddLine(" ")
            tt:AddLine("Hook Criteria")
        end

        if q.HookScript and q.HookScript ~= "" then
            local Text = Format(q.HookScript)
            tt:AddLine(fmt.FormatCode(Text, 0, SyntaxColors), 1, 1, 1, 1)
        end

        if q.HookElements then
            local applied = false
            for k, v in orderedPairs(q.HookElements) do
                if not applied then
                    tt:AddLine(" ")
                    tt:AddLine("Values")
                    applied = true
                end
                if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then
                    tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1)
                end
            end
        end
    end

    if q.ReadyScript and q.ReadyScript ~= "" then
        tt:AddLine(" ")
        tt:AddLine("Time Script")

        tt:AddLine(fmt.FormatCode(q.ReadyScript, 0, SyntaxColors), 1, 1, 1, 1)

        if q.ReadyElements then
            tt:AddLine("Values")
            for k, v in orderedPairs(q.ReadyElements) do
                if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then
                    tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1)
                end
            end
        end
    end

    if q.ActScript and q.ActScript ~= "" then
        tt:AddLine(" ")
        tt:AddLine("Action Criteria")

        tt:AddLine(fmt.FormatCode(q.ActScript, 0, SyntaxColors), 1, 1, 1, 1)

        if q.ActElements then
            tt:AddLine(" ")
            tt:AddLine("Values")
            for k, v in orderedPairs(q.ActElements) do
                if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then
                    tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1)
                end
            end
        end
    end

    if q.pack and q.listName and q.action then
        local entry = rawget( self.DB.profile.packs, q.pack )
        entry = entry and entry.lists[ q.listName ]
        entry = entry and entry[ q.action ]

        if entry and entry.description and entry.description:len() > 0 then
            tt:AddLine( " " )
            tt:AddLine( entry.description, 0, 0.7, 1, true )
        end
    end

    tt:SetMinimumWidth( 400 )
    tt:Show()
end

function Hekili:SaveCoordinates()
    for i in pairs(Hekili.DB.profile.displays) do
        local display = ns.UI.Displays[i]
        if display then
            local rel, x, y = select( 3, display:GetPoint() )

            self.DB.profile.displays[i].rel = "CENTER"
            self.DB.profile.displays[i].x = x
            self.DB.profile.displays[i].y = y
        end
    end

    self.DB.profile.notifications.x, self.DB.profile.notifications.y = select( 4, HekiliNotification:GetPoint() )
end
